From 66a4e5122a2d810ada2f23af803d1e44256dd260 Mon Sep 17 00:00:00 2001 From: Nikhita Raghunath Date: Wed, 22 Nov 2017 16:34:36 +0530 Subject: [PATCH 1/3] bump(go-openapi/validate): d509235108fcf6ab4913d2dcb3a2260c0db2108e --- Godeps/Godeps.json | 2 +- .../github.com/go-openapi/validate/.drone.sec | 1 - .../github.com/go-openapi/validate/.drone.yml | 40 --------- .../go-openapi/validate/.pullapprove.yml | 13 --- .../go-openapi/validate/.travis.yml | 22 +++++ .../github.com/go-openapi/validate/README.md | 2 +- .../github.com/go-openapi/validate/formats.go | 5 +- .../go-openapi/validate/object_validator.go | 73 ++++++++++++--- .../github.com/go-openapi/validate/result.go | 30 ++++++- .../github.com/go-openapi/validate/schema.go | 48 ++++++++-- .../go-openapi/validate/schema_props.go | 17 +++- .../go-openapi/validate/slice_validator.go | 3 + vendor/github.com/go-openapi/validate/spec.go | 88 +++++++++++++++---- vendor/github.com/go-openapi/validate/type.go | 19 ++-- .../go-openapi/validate/update-fixtures.sh | 15 ++++ .../go-openapi/validate/validator.go | 33 +++++-- .../github.com/go-openapi/validate/values.go | 6 +- 17 files changed, 301 insertions(+), 116 deletions(-) delete mode 100644 vendor/github.com/go-openapi/validate/.drone.sec delete mode 100644 vendor/github.com/go-openapi/validate/.drone.yml delete mode 100644 vendor/github.com/go-openapi/validate/.pullapprove.yml create mode 100644 vendor/github.com/go-openapi/validate/.travis.yml create mode 100755 vendor/github.com/go-openapi/validate/update-fixtures.sh diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 12c252b516b..6a90e23829d 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1263,7 +1263,7 @@ }, { "ImportPath": "github.com/go-openapi/validate", - "Rev": "deaf2c9013bc1a7f4c774662259a506ba874d80f" + "Rev": "d509235108fcf6ab4913d2dcb3a2260c0db2108e" }, { "ImportPath": "github.com/godbus/dbus", diff --git a/vendor/github.com/go-openapi/validate/.drone.sec b/vendor/github.com/go-openapi/validate/.drone.sec deleted file mode 100644 index 64bfa2ef6e9..00000000000 --- a/vendor/github.com/go-openapi/validate/.drone.sec +++ /dev/null @@ -1 +0,0 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.lisG21ATunZSCBP6vaqK_AZCIR18tN563RdqkAb6PGOipqTeg8R7VwZQIeDqS-2Vond6NX_KSC_D_uxxv0hBf2DiGXmwMUmP4nRXrsmbzT2qQKKIHYRDC_6jb2-FSfK14ezIe1Q07UiiJecDsN3CFEccS8E68Tdnp78p7yDwbTvpumnZmwYfyhlImtjFQv2YpyFVsjEHWK0R4e9T3ONQWcx6D2rSoxABbutrS03QwsJhHCeD9joL_gxfkFKm3CW8yWPSk2QYtx_Q1hu-tZR4IPb2tQPXPX3mtyhwBqziWgmJRDFCEjlCO5aCobiMm_9K5X05gue_DcgW163zh1P9jg.nleER2An8CUn_OuR.b77RFEFp0gC8j5yCAoARNKYmQIvWq99ibmf5ffJgdhQBF3sRYJLt_XflJ_2lsaiFOxvc45T2fnkMVy2lHFcri7F9f1BRiT_0AcDthxsecGzG8BZ9QvaM6b4Dn0rhjrOq8rsF0m3ZnbPBkkg3LV5EkbHWstMo2fgJPJhJswlGWhqJPJBDecG1nMBC8SMH32X-zVlSM-BLiaghvOGNxyb_RLZJZ3CLczIdQ2JO2UeYkOGCPGzernvkHDMpqQXc-8cmulDdHgCy87qFLy5ttGFgYbnTm92h_ChOGKZixeX0PL0pQY5wXd2xTO7Tg_Ov5E5FoVwIkwOextedVsF9iz_b_mwtCY3LXrvbJTW7zWrwBVsVyAXxT5iu0HyQ3tBVxT2GxS-yM5ApqLozcZCQg9flMyfSgThu82FfzEr0fI5vKw8zo0GdO4GBuVSppM9m6ToG6hlwyHD9g2YTZw9068hyq1_kZQhugJRjgGbpa2gyGqzx16fg0zVoupVIiq5KfvRlAQFeOVVjQwb0BWf25tJUj5tV3O9ge6dbKSXizEca33FJJwJWoXhd7DCREXUU9pBz06NCCf495BGoVbq3oLPDQc2mpcuy0XAPxSwXcc5Ts8DNs7MrxBlYdw81wMXuztIpOY4.XjKlMWl_H40XszToi2VU5g \ No newline at end of file diff --git a/vendor/github.com/go-openapi/validate/.drone.yml b/vendor/github.com/go-openapi/validate/.drone.yml deleted file mode 100644 index cfe9c7c3005..00000000000 --- a/vendor/github.com/go-openapi/validate/.drone.yml +++ /dev/null @@ -1,40 +0,0 @@ -clone: - path: github.com/go-openapi/validate - -matrix: - GO_VERSION: - - "1.6" - -build: - integration: - image: golang:$$GO_VERSION - pull: true - commands: - - go get -u github.com/axw/gocov/gocov - - go get -u gopkg.in/matm/v1/gocov-html - - go get -u github.com/cee-dub/go-junit-report - - go get -u github.com/stretchr/testify/assert - - go get -u gopkg.in/yaml.v2 - - go get -u github.com/go-openapi/analysis - - go get -u github.com/go-openapi/errors - - go get -u github.com/go-openapi/loads - - go get -u github.com/go-openapi/strfmt - - go get -u github.com/go-openapi/runtime - - go test -race - - go test -v -cover -coverprofile=coverage.out -covermode=count - -notify: - slack: - channel: bots - webhook_url: $$SLACK_URL - username: drone - -publish: - coverage: - server: https://coverage.vmware.run - token: $$GITHUB_TOKEN - # threshold: 70 - # must_increase: true - when: - matrix: - GO_VERSION: "1.6" diff --git a/vendor/github.com/go-openapi/validate/.pullapprove.yml b/vendor/github.com/go-openapi/validate/.pullapprove.yml deleted file mode 100644 index 5ec183e2244..00000000000 --- a/vendor/github.com/go-openapi/validate/.pullapprove.yml +++ /dev/null @@ -1,13 +0,0 @@ -approve_by_comment: true -approve_regex: '^(:shipit:|:\+1:|\+1|LGTM|lgtm|Approved)' -reject_regex: ^[Rr]ejected -reset_on_push: false -reviewers: - members: - - casualjim - - chancez - - frapposelli - - vburenin - - pytlesk4 - name: pullapprove - required: 1 diff --git a/vendor/github.com/go-openapi/validate/.travis.yml b/vendor/github.com/go-openapi/validate/.travis.yml new file mode 100644 index 00000000000..6eeb6f62675 --- /dev/null +++ b/vendor/github.com/go-openapi/validate/.travis.yml @@ -0,0 +1,22 @@ +language: go +go: +- 1.7 +install: +- go get -u github.com/axw/gocov/gocov +- go get -u gopkg.in/matm/v1/gocov-html +- go get -u github.com/cee-dub/go-junit-report +- go get -u github.com/stretchr/testify/assert +- go get -u github.com/kr/pretty +- go get -u gopkg.in/yaml.v2 +- go get -u github.com/go-openapi/analysis +- go get -u github.com/go-openapi/errors +- go get -u github.com/go-openapi/loads +- go get -u github.com/go-openapi/strfmt +- go get -u github.com/go-openapi/runtime +script: +- go test -v -race -cover -coverprofile=coverage.txt -covermode=atomic +after_success: +- bash <(curl -s https://codecov.io/bash) +notifications: + slack: + secure: EmObnQuM9Mw8J9vpFaKKHqSMN4Wsr/A9+v7ewAD5cEhA0T1P4m7MbJMiJOhxUhj/X+BFh2DamW+P2lT8mybj5wg8wnkQ2BteKA8Tawi6f9PRw2NRheO8tAi8o/npLnlmet0kc93mn+oLuqHw36w4+j5mkOl2FghkfGiUVhwrhkCP7KXQN+3TU87e+/HzQumlJ3nsE+6terVxkH3PmaUTsS5ONaODZfuxFpfb7RsoEl3skHf6d+tr+1nViLxxly7558Nc33C+W1mr0qiEvMLZ+kJ/CpGWBJ6CUJM3jm6hNe2eMuIPwEK2hxZob8c7n22VPap4K6a0bBRoydoDXaba+2sD7Ym6ivDO/DVyL44VeBBLyIiIBylDGQdZH+6SoWm90Qe/i7tnY/T5Ao5igT8f3cfQY1c3EsTfqmlDfrhmACBmwSlgkdVBLTprHL63JMY24LWmh4jhxsmMRZhCL4dze8su1w6pLN/pD1pGHtKYCEVbdTmaM3PblNRFf12XB7qosmQsgUndH4Vq3bTbU0s1pKjeDhRyLvFzvR0TBbo0pDLEoF1A/i5GVFWa7yLZNUDudQERRh7qv/xBl2excIaQ1sV4DSVm7bAE9l6Kp+yeHQJW2uN6Y3X8wu9gB9nv9l5HBze7wh8KE6PyWAOLYYqZg9/sAtsv/2GcQqXcKFF1zcA= diff --git a/vendor/github.com/go-openapi/validate/README.md b/vendor/github.com/go-openapi/validate/README.md index 7e7831c9ea4..e3bfc622be2 100644 --- a/vendor/github.com/go-openapi/validate/README.md +++ b/vendor/github.com/go-openapi/validate/README.md @@ -1,3 +1,3 @@ -# Validation helpers [![Build Status](https://ci.vmware.run/api/badges/go-openapi/validate/status.svg)](https://ci.vmware.run/go-openapi/validate) [![Coverage](https://coverage.vmware.run/badges/go-openapi/validate/coverage.svg)](https://coverage.vmware.run/go-openapi/validate) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io) +# Validation helpers [![Build Status](https://travis-ci.org/go-openapi/validate.svg?branch=master)](https://travis-ci.org/go-openapi/validate) [![codecov](https://codecov.io/gh/go-openapi/validate/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/validate) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io) [![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/validate/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/validate?status.svg)](http://godoc.org/github.com/go-openapi/validate) diff --git a/vendor/github.com/go-openapi/validate/formats.go b/vendor/github.com/go-openapi/validate/formats.go index f0e9f9039a1..294845aff38 100644 --- a/vendor/github.com/go-openapi/validate/formats.go +++ b/vendor/github.com/go-openapi/validate/formats.go @@ -15,6 +15,7 @@ package validate import ( + "log" "reflect" "github.com/go-openapi/spec" @@ -51,7 +52,9 @@ func (f *formatValidator) Applies(source interface{}, kind reflect.Kind) bool { return false } r := doit() - // fmt.Printf("schema props validator for %q applies %t for %T (kind: %v)\n", f.Path, r, source, kind) + if Debug { + log.Printf("format validator for %q applies %t for %T (kind: %v)\n", f.Path, r, source, kind) + } return r } diff --git a/vendor/github.com/go-openapi/validate/object_validator.go b/vendor/github.com/go-openapi/validate/object_validator.go index 7caff230c51..0dd580288df 100644 --- a/vendor/github.com/go-openapi/validate/object_validator.go +++ b/vendor/github.com/go-openapi/validate/object_validator.go @@ -15,8 +15,10 @@ package validate import ( + "log" "reflect" "regexp" + "strings" "github.com/go-openapi/errors" "github.com/go-openapi/spec" @@ -45,10 +47,45 @@ func (o *objectValidator) Applies(source interface{}, kind reflect.Kind) bool { // there is a problem in the type validator where it will be unhappy about null values // so that requires more testing r := reflect.TypeOf(source) == specSchemaType && (kind == reflect.Map || kind == reflect.Struct) - //fmt.Printf("object validator for %q applies %t for %T (kind: %v)\n", o.Path, r, source, kind) + if Debug { + log.Printf("object validator for %q applies %t for %T (kind: %v)\n", o.Path, r, source, kind) + } return r } +func (o *objectValidator) isPropertyName() bool { + p := strings.Split(o.Path, ".") + return p[len(p)-1] == "properties" && p[len(p)-2] != "properties" +} +func (o *objectValidator) checkArrayMustHaveItems(res *Result, val map[string]interface{}) { + if t, typeFound := val["type"]; typeFound { + if tpe, ok := t.(string); ok && tpe == "array" { + if _, itemsKeyFound := val["items"]; !itemsKeyFound { + res.AddErrors(errors.Required("items", o.Path)) + } + } + } +} + +func (o *objectValidator) checkItemsMustBeTypeArray(res *Result, val map[string]interface{}) { + if !o.isPropertyName() { + if _, itemsKeyFound := val["items"]; itemsKeyFound { + t, typeFound := val["type"] + if typeFound { + if tpe, ok := t.(string); !ok || tpe != "array" { + res.AddErrors(errors.InvalidType(o.Path, o.In, "array", nil)) + } + } else { + // there is no type + res.AddErrors(errors.Required("type", o.Path)) + } + } + } +} +func (o *objectValidator) precheck(res *Result, val map[string]interface{}) { + o.checkArrayMustHaveItems(res, val) + o.checkItemsMustBeTypeArray(res, val) +} func (o *objectValidator) Validate(data interface{}) *Result { val := data.(map[string]interface{}) numKeys := int64(len(val)) @@ -61,14 +98,8 @@ func (o *objectValidator) Validate(data interface{}) *Result { } res := new(Result) - if len(o.Required) > 0 { - for _, k := range o.Required { - if _, ok := val[k]; !ok { - res.AddErrors(errors.Required(o.Path+"."+k, o.In)) - continue - } - } - } + + o.precheck(res, val) if o.AdditionalProperties != nil && !o.AdditionalProperties.Allows { for k := range val { @@ -99,6 +130,8 @@ func (o *objectValidator) Validate(data interface{}) *Result { } } + createdFromDefaults := map[string]bool{} + for pName, pSchema := range o.Properties { rName := pName if o.Path != "" { @@ -106,7 +139,24 @@ func (o *objectValidator) Validate(data interface{}) *Result { } if v, ok := val[pName]; ok { - res.Merge(NewSchemaValidator(&pSchema, o.Root, rName, o.KnownFormats).Validate(v)) + r := NewSchemaValidator(&pSchema, o.Root, rName, o.KnownFormats).Validate(v) + res.Merge(r) + } else if pSchema.Default != nil { + createdFromDefaults[pName] = true + pName := pName // shaddow + def := pSchema.Default + res.Defaulters = append(res.Defaulters, DefaulterFunc(func() { + val[pName] = def + })) + } + } + + if len(o.Required) > 0 { + for _, k := range o.Required { + if _, ok := val[k]; !ok && !createdFromDefaults[k] { + res.AddErrors(errors.Required(o.Path+"."+k, o.In)) + continue + } } } @@ -137,9 +187,6 @@ func (o *objectValidator) validatePatternProperty(key string, value interface{}, res := validator.Validate(value) result.Merge(res) - if res.IsValid() { - succeededOnce = true - } } } diff --git a/vendor/github.com/go-openapi/validate/result.go b/vendor/github.com/go-openapi/validate/result.go index d8f71e9468b..7b58e50df71 100644 --- a/vendor/github.com/go-openapi/validate/result.go +++ b/vendor/github.com/go-openapi/validate/result.go @@ -14,12 +14,32 @@ package validate -import "github.com/go-openapi/errors" +import ( + "os" + + "github.com/go-openapi/errors" +) + +var ( + // Debug is true when the SWAGGER_DEBUG env var is not empty + Debug = os.Getenv("SWAGGER_DEBUG") != "" +) + +type Defaulter interface { + Apply() +} + +type DefaulterFunc func() + +func (f DefaulterFunc) Apply() { + f() +} // Result represents a validation result type Result struct { Errors []error MatchCount int + Defaulters []Defaulter } // Merge merges this result with the other one, preserving match counts etc @@ -29,11 +49,13 @@ func (r *Result) Merge(other *Result) *Result { } r.AddErrors(other.Errors...) r.MatchCount += other.MatchCount + r.Defaulters = append(r.Defaulters, other.Defaulters...) return r } // AddErrors adds errors to this validation result func (r *Result) AddErrors(errors ...error) { + // TODO: filter already existing errors r.Errors = append(r.Errors, errors...) } @@ -59,3 +81,9 @@ func (r *Result) AsError() error { } return errors.CompositeValidationError(r.Errors...) } + +func (r *Result) ApplyDefaults() { + for _, d := range r.Defaulters { + d.Apply() + } +} diff --git a/vendor/github.com/go-openapi/validate/schema.go b/vendor/github.com/go-openapi/validate/schema.go index 8bf7d3bcc9b..f859c6d6309 100644 --- a/vendor/github.com/go-openapi/validate/schema.go +++ b/vendor/github.com/go-openapi/validate/schema.go @@ -15,6 +15,8 @@ package validate import ( + "encoding/json" + "log" "reflect" "github.com/go-openapi/spec" @@ -80,12 +82,16 @@ func (s *SchemaValidator) Applies(source interface{}, kind reflect.Kind) bool { // Validate validates the data against the schema func (s *SchemaValidator) Validate(data interface{}) *Result { + result := new(Result) + if s == nil { + return result + } + if data == nil { v := s.validators[0].Validate(data) v.Merge(s.validators[6].Validate(data)) return v } - result := new(Result) tpe := reflect.TypeOf(data) kind := tpe.Kind() @@ -98,8 +104,35 @@ func (s *SchemaValidator) Validate(data interface{}) *Result { d = swag.ToDynamicJSON(data) } + isnumber := s.Schema.Type.Contains("number") || s.Schema.Type.Contains("integer") + if num, ok := data.(json.Number); ok && isnumber { + if s.Schema.Type.Contains("integer") { // avoid lossy conversion + in, erri := num.Int64() + if erri != nil { + result.AddErrors(erri) + result.Inc() + return result + } + d = in + } else { + nf, errf := num.Float64() + if errf != nil { + result.AddErrors(errf) + result.Inc() + return result + } + d = nf + } + + tpe = reflect.TypeOf(d) + kind = tpe.Kind() + } + for _, v := range s.validators { if !v.Applies(s.Schema, kind) { + if Debug { + log.Printf("%T does not apply for %v", v, kind) + } continue } @@ -117,10 +150,9 @@ func (s *SchemaValidator) typeValidator() valueValidator { func (s *SchemaValidator) commonValidator() valueValidator { return &basicCommonValidator{ - Path: s.Path, - In: s.in, - Default: s.Schema.Default, - Enum: s.Schema.Enum, + Path: s.Path, + In: s.in, + Enum: s.Schema.Enum, } } @@ -155,7 +187,6 @@ func (s *SchemaValidator) stringValidator() valueValidator { return &stringValidator{ Path: s.Path, In: s.in, - Default: s.Schema.Default, MaxLength: s.Schema.MaxLength, MinLength: s.Schema.MinLength, Pattern: s.Schema.Pattern, @@ -164,9 +195,8 @@ func (s *SchemaValidator) stringValidator() valueValidator { func (s *SchemaValidator) formatValidator() valueValidator { return &formatValidator{ - Path: s.Path, - In: s.in, - //Default: s.Schema.Default, + Path: s.Path, + In: s.in, Format: s.Schema.Format, KnownFormats: s.KnownFormats, } diff --git a/vendor/github.com/go-openapi/validate/schema_props.go b/vendor/github.com/go-openapi/validate/schema_props.go index feff6d6a37a..5d8ed404521 100644 --- a/vendor/github.com/go-openapi/validate/schema_props.go +++ b/vendor/github.com/go-openapi/validate/schema_props.go @@ -15,6 +15,7 @@ package validate import ( + "log" "reflect" "github.com/go-openapi/errors" @@ -80,12 +81,15 @@ func newSchemaPropsValidator(path string, in string, allOf, oneOf, anyOf []spec. func (s *schemaPropsValidator) Applies(source interface{}, kind reflect.Kind) bool { r := reflect.TypeOf(source) == specSchemaType - // fmt.Printf("schema props validator for %q applies %t for %T (kind: %v)\n", s.Path, r, source, kind) + if Debug { + log.Printf("schema props validator for %q applies %t for %T (kind: %v)\n", s.Path, r, source, kind) + } return r } func (s *schemaPropsValidator) Validate(data interface{}) *Result { mainResult := new(Result) + var firstSuccess *Result if len(s.anyOfValidators) > 0 { var bestFailures *Result succeededOnce := false @@ -94,6 +98,9 @@ func (s *schemaPropsValidator) Validate(data interface{}) *Result { if result.IsValid() { bestFailures = nil succeededOnce = true + if firstSuccess == nil { + firstSuccess = result + } break } if bestFailures == nil || result.MatchCount > bestFailures.MatchCount { @@ -106,11 +113,14 @@ func (s *schemaPropsValidator) Validate(data interface{}) *Result { } if bestFailures != nil { mainResult.Merge(bestFailures) + } else if firstSuccess != nil { + mainResult.Merge(firstSuccess) } } if len(s.oneOfValidators) > 0 { var bestFailures *Result + var firstSuccess *Result validated := 0 for _, oneOfSchema := range s.oneOfValidators { @@ -118,6 +128,9 @@ func (s *schemaPropsValidator) Validate(data interface{}) *Result { if result.IsValid() { validated++ bestFailures = nil + if firstSuccess == nil { + firstSuccess = result + } continue } if validated == 0 && (bestFailures == nil || result.MatchCount > bestFailures.MatchCount) { @@ -130,6 +143,8 @@ func (s *schemaPropsValidator) Validate(data interface{}) *Result { if bestFailures != nil { mainResult.Merge(bestFailures) } + } else if firstSuccess != nil { + mainResult.Merge(firstSuccess) } } diff --git a/vendor/github.com/go-openapi/validate/slice_validator.go b/vendor/github.com/go-openapi/validate/slice_validator.go index 8371061535d..2665a0ffe99 100644 --- a/vendor/github.com/go-openapi/validate/slice_validator.go +++ b/vendor/github.com/go-openapi/validate/slice_validator.go @@ -67,6 +67,9 @@ func (s *schemaSliceValidator) Validate(data interface{}) *Result { itemsSize = int64(len(s.Items.Schemas)) for i := int64(0); i < itemsSize; i++ { validator := NewSchemaValidator(&s.Items.Schemas[i], s.Root, fmt.Sprintf("%s.%d", s.Path, i), s.KnownFormats) + if val.Len() <= int(i) { + break + } result.Merge(validator.Validate(val.Index(int(i)).Interface())) } diff --git a/vendor/github.com/go-openapi/validate/spec.go b/vendor/github.com/go-openapi/validate/spec.go index 938bc5bf907..2f1d7028695 100644 --- a/vendor/github.com/go-openapi/validate/spec.go +++ b/vendor/github.com/go-openapi/validate/spec.go @@ -19,6 +19,7 @@ import ( "fmt" "log" "regexp" + "strconv" "strings" "github.com/go-openapi/analysis" @@ -193,6 +194,13 @@ func (s *SpecValidator) validateDuplicatePropertyNames() *Result { return res } +func (s *SpecValidator) resolveRef(ref *spec.Ref) (*spec.Schema, error) { + if s.spec.SpecFilePath() != "" { + return spec.ResolveRefWithBase(s.spec.Spec(), ref, &spec.ExpandOptions{RelativeBase: s.spec.SpecFilePath()}) + } + return spec.ResolveRef(s.spec.Spec(), ref) +} + func (s *SpecValidator) validateSchemaPropertyNames(nm string, sch spec.Schema, knowns map[string]struct{}) []dupProp { var dups []dupProp @@ -200,7 +208,7 @@ func (s *SpecValidator) validateSchemaPropertyNames(nm string, sch spec.Schema, schc := &sch for schc.Ref.String() != "" { // gather property names - reso, err := spec.ResolveRef(s.spec.Spec(), &schc.Ref) + reso, err := s.resolveRef(&schc.Ref) if err != nil { panic(err) } @@ -236,7 +244,7 @@ func (s *SpecValidator) validateCircularAncestry(nm string, sch spec.Schema, kno schn := nm schc := &sch for schc.Ref.String() != "" { - reso, err := spec.ResolveRef(s.spec.Spec(), &schc.Ref) + reso, err := s.resolveRef(&schc.Ref) if err != nil { panic(err) } @@ -335,15 +343,15 @@ func (s *SpecValidator) validateSchemaItems(schema spec.Schema, prefix, opID str return errors.New(422, "%s for %q is a collection without an element type", prefix, opID) } - schemas := schema.Items.Schemas if schema.Items.Schema != nil { - schemas = []spec.Schema{*schema.Items.Schema} - } - for _, sch := range schemas { - if err := s.validateSchemaItems(sch, prefix, opID); err != nil { - return err + schema = *schema.Items.Schema + if _, err := regexp.Compile(schema.Pattern); err != nil { + return errors.New(422, "%s for %q has invalid items pattern: %q", prefix, opID, schema.Pattern) } + + return s.validateSchemaItems(schema, prefix, opID) } + return nil } @@ -524,8 +532,15 @@ func (s *SpecValidator) validateParameters() *Result { } var fromPath []string for _, i := range params { - fromPath = append(fromPath, knowns[i]) - knowns[i] = "!" + knownsi := knowns[i] + iparams := extractPathParams(knownsi) + if len(iparams) > 0 { + fromPath = append(fromPath, iparams...) + for _, iparam := range iparams { + knownsi = strings.Replace(knownsi, iparam, "!", 1) + } + knowns[i] = knownsi + } } knownPath := strings.Join(knowns, "/") if orig, ok := knownPaths[knownPath]; ok { @@ -544,7 +559,9 @@ func (s *SpecValidator) validateParameters() *Result { for pr.Ref.String() != "" { obj, _, err := pr.Ref.GetPointer().Get(sw) if err != nil { - log.Println(err) + if Debug { + log.Println(err) + } res.AddErrors(err) break PARAMETERS } @@ -575,6 +592,10 @@ func (s *SpecValidator) validateParameters() *Result { pr = obj.(spec.Parameter) } + if _, err := regexp.Compile(pr.Pattern); err != nil { + res.AddErrors(errors.New(422, "operation %q has invalid pattern in param %q: %q", op.ID, pr.Name, pr.Pattern)) + } + if pr.In == "body" { if firstBodyParam != "" { res.AddErrors(errors.New(422, "operation %q has more than 1 body param (accepted: %q, dropped: %q)", op.ID, firstBodyParam, pr.Name)) @@ -595,18 +616,35 @@ func (s *SpecValidator) validateParameters() *Result { func parsePath(path string) (segments []string, params []int) { for i, p := range strings.Split(path, "/") { segments = append(segments, p) - if len(p) > 0 && p[0] == '{' && p[len(p)-1] == '}' { + if d0 := strings.Index(p, "{"); d0 >= 0 && d0 < strings.Index(p, "}") { params = append(params, i) } } return } +func extractPathParams(segment string) (params []string) { + for { + d0 := strings.IndexByte(segment, '{') + if d0 < 0 { + break + } + d1 := strings.IndexByte(segment[d0:], '}') + if d1 > 0 { + params = append(params, segment[d0:d0+d1+1]) + } else { + break + } + segment = segment[d1:] + } + return params +} + func (s *SpecValidator) validateReferencesValid() *Result { // each reference must point to a valid object res := new(Result) for _, r := range s.analyzer.AllRefs() { - if !r.IsValidURI() { + if !r.IsValidURI(s.spec.SpecFilePath()) { res.AddErrors(errors.New(404, "invalid ref %q", r.String())) } } @@ -698,7 +736,9 @@ func (s *SpecValidator) validateDefaultValueValidAgainstSchema() *Result { } // check simple paramters first if param.Default != nil && param.Schema == nil { - //fmt.Println(param.Name, "in", param.In, "has a default without a schema") + if Debug { + log.Println(param.Name, "in", param.In, "has a default without a schema") + } // check param valid res.Merge(NewParamValidator(¶m, s.KnownFormats).Validate(param.Default)) } @@ -721,9 +761,15 @@ func (s *SpecValidator) validateDefaultValueValidAgainstSchema() *Result { if h.Items != nil { res.Merge(s.validateDefaultValueItemsAgainstSchema(nm, "header", &h, h.Items)) } + if _, err := regexp.Compile(h.Pattern); err != nil { + res.AddErrors(errors.New(422, "operation %q has invalid pattern in default header %q: %q", op.ID, nm, h.Pattern)) + } + } + if dr.Schema != nil { + res.Merge(s.validateDefaultValueSchemaAgainstSchema("default", "response", dr.Schema)) } } - for _, r := range op.Responses.StatusCodeResponses { + for code, r := range op.Responses.StatusCodeResponses { for nm, h := range r.Headers { if h.Default != nil { res.Merge(NewHeaderValidator(nm, &h, s.KnownFormats).Validate(h.Default)) @@ -731,6 +777,12 @@ func (s *SpecValidator) validateDefaultValueValidAgainstSchema() *Result { if h.Items != nil { res.Merge(s.validateDefaultValueItemsAgainstSchema(nm, "header", &h, h.Items)) } + if _, err := regexp.Compile(h.Pattern); err != nil { + res.AddErrors(errors.New(422, "operation %q has invalid pattern in %v's header %q: %q", op.ID, code, nm, h.Pattern)) + } + } + if r.Schema != nil { + res.Merge(s.validateDefaultValueSchemaAgainstSchema(strconv.Itoa(code), "response", r.Schema)) } } @@ -758,6 +810,9 @@ func (s *SpecValidator) validateDefaultValueSchemaAgainstSchema(path, in string, res.Merge(s.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.items[%d]", path, i), in, &sch)) } } + if _, err := regexp.Compile(schema.Pattern); err != nil { + res.AddErrors(errors.New(422, "%s in %s has invalid pattern: %q", path, in, schema.Pattern)) + } if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil { res.Merge(s.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.additionalItems", path), in, schema.AdditionalItems.Schema)) } @@ -787,6 +842,9 @@ func (s *SpecValidator) validateDefaultValueItemsAgainstSchema(path, in string, if items.Items != nil { res.Merge(s.validateDefaultValueItemsAgainstSchema(path+"[0]", in, root, items.Items)) } + if _, err := regexp.Compile(items.Pattern); err != nil { + res.AddErrors(errors.New(422, "%s in %s has invalid pattern: %q", path, in, items.Pattern)) + } } return res } diff --git a/vendor/github.com/go-openapi/validate/type.go b/vendor/github.com/go-openapi/validate/type.go index 0a5f4a2932a..3ae31f087a5 100644 --- a/vendor/github.com/go-openapi/validate/type.go +++ b/vendor/github.com/go-openapi/validate/type.go @@ -15,6 +15,7 @@ package validate import ( + "log" "reflect" "strings" @@ -32,16 +33,6 @@ type typeValidator struct { Path string } -var jsonTypeNames = map[string]struct{}{ - "array": struct{}{}, - "boolean": struct{}{}, - "integer": struct{}{}, - "null": struct{}{}, - "number": struct{}{}, - "object": struct{}{}, - "string": struct{}{}, -} - func (t *typeValidator) schemaInfoForType(data interface{}) (string, string) { switch data.(type) { case []byte: @@ -121,7 +112,9 @@ func (t *typeValidator) SetPath(path string) { func (t *typeValidator) Applies(source interface{}, kind reflect.Kind) bool { stpe := reflect.TypeOf(source) r := (len(t.Type) > 0 || t.Format != "") && (stpe == specSchemaType || stpe == specParameterType || stpe == specHeaderType) - //fmt.Printf("type validator for %q applies %t for %T (kind: %v)\n", t.Path, r, source, kind) + if Debug { + log.Printf("type validator for %q applies %t for %T (kind: %v)\n", t.Path, r, source, kind) + } return r } @@ -140,7 +133,9 @@ func (t *typeValidator) Validate(data interface{}) *Result { kind := val.Kind() schType, format := t.schemaInfoForType(data) - //fmt.Println("path:", t.Path, "schType:", schType, "format:", format, "expType:", t.Type, "expFmt:", t.Format, "kind:", val.Kind().String()) + if Debug { + log.Println("path:", t.Path, "schType:", schType, "format:", format, "expType:", t.Type, "expFmt:", t.Format, "kind:", val.Kind().String()) + } isLowerInt := t.Format == "int64" && format == "int32" isLowerFloat := t.Format == "float64" && format == "float32" isFloatInt := schType == "number" && swag.IsFloat64AJSONInteger(val.Float()) && t.Type.Contains("integer") diff --git a/vendor/github.com/go-openapi/validate/update-fixtures.sh b/vendor/github.com/go-openapi/validate/update-fixtures.sh new file mode 100755 index 00000000000..21b06e2b09a --- /dev/null +++ b/vendor/github.com/go-openapi/validate/update-fixtures.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -eu -o pipefail +dir=$(git rev-parse --show-toplevel) +scratch=$(mktemp -d -t tmp.XXXXXXXXXX) + +function finish { + rm -rf "$scratch" +} +trap finish EXIT SIGHUP SIGINT SIGTERM + +cd "$scratch" +git clone https://github.com/json-schema-org/JSON-Schema-Test-Suite Suite +cp -r Suite/tests/draft4/* "$dir/fixtures/jsonschema_suite" +cp -a Suite/remotes "$dir/fixtures/jsonschema_suite" diff --git a/vendor/github.com/go-openapi/validate/validator.go b/vendor/github.com/go-openapi/validate/validator.go index 45675d28f31..64a379fcde1 100644 --- a/vendor/github.com/go-openapi/validate/validator.go +++ b/vendor/github.com/go-openapi/validate/validator.go @@ -16,6 +16,7 @@ package validate import ( "fmt" + "log" "reflect" "github.com/go-openapi/errors" @@ -156,8 +157,15 @@ func (b *basicCommonValidator) Applies(source interface{}, kind reflect.Kind) bo func (b *basicCommonValidator) Validate(data interface{}) (res *Result) { if len(b.Enum) > 0 { for _, enumValue := range b.Enum { - if data != nil && reflect.DeepEqual(enumValue, data) { - return nil + actualType := reflect.TypeOf(enumValue) + if actualType == nil { + continue + } + expectedValue := reflect.ValueOf(data) + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + if reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), enumValue) { + return nil + } } } return sErr(errors.EnumFail(b.Path, b.In, data, b.Enum)) @@ -474,10 +482,14 @@ func (n *numberValidator) Applies(source interface{}, kind reflect.Kind) bool { isInt := kind >= reflect.Int && kind <= reflect.Uint64 isFloat := kind == reflect.Float32 || kind == reflect.Float64 r := isInt || isFloat - // fmt.Printf("schema props validator for %q applies %t for %T (kind: %v)\n", n.Path, r, source, kind) + if Debug { + log.Printf("schema props validator for %q applies %t for %T (kind: %v)\n", n.Path, r, source, kind) + } return r } - // fmt.Printf("schema props validator for %q applies %t for %T (kind: %v)\n", n.Path, false, source, kind) + if Debug { + log.Printf("schema props validator for %q applies %t for %T (kind: %v)\n", n.Path, false, source, kind) + } return false } @@ -536,15 +548,22 @@ func (s *stringValidator) Applies(source interface{}, kind reflect.Kind) bool { switch source.(type) { case *spec.Parameter, *spec.Schema, *spec.Items, *spec.Header: r := kind == reflect.String - // fmt.Printf("string validator for %q applies %t for %T (kind: %v)\n", s.Path, r, source, kind) + if Debug { + log.Printf("string validator for %q applies %t for %T (kind: %v)\n", s.Path, r, source, kind) + } return r } - // fmt.Printf("string validator for %q applies %t for %T (kind: %v)\n", s.Path, false, source, kind) + if Debug { + log.Printf("string validator for %q applies %t for %T (kind: %v)\n", s.Path, false, source, kind) + } return false } func (s *stringValidator) Validate(val interface{}) *Result { - data := val.(string) + data, ok := val.(string) + if !ok { + return sErr(errors.InvalidType(s.Path, s.In, "string", val)) + } if s.Required && !s.AllowEmptyValue && (s.Default == nil || s.Default == "") { if err := RequiredString(s.Path, s.In, data); err != nil { diff --git a/vendor/github.com/go-openapi/validate/values.go b/vendor/github.com/go-openapi/validate/values.go index ab5ce8af508..67895d1ec60 100644 --- a/vendor/github.com/go-openapi/validate/values.go +++ b/vendor/github.com/go-openapi/validate/values.go @@ -196,7 +196,11 @@ func MinimumUint(path, in string, data, min uint64, exclusive bool) *errors.Vali // MultipleOf validates if the provided number is a multiple of the factor func MultipleOf(path, in string, data, factor float64) *errors.Validation { - if !swag.IsFloat64AJSONInteger(data / factor) { + mult := data / factor + if factor < 1 { + mult = 1 / factor * data + } + if !swag.IsFloat64AJSONInteger(mult) { return errors.NotMultipleOf(path, in, factor) } return nil From 6fbe8157e39f6bd7ad885a8a6f8614e2fe45dc5e Mon Sep 17 00:00:00 2001 From: Nikhita Raghunath Date: Mon, 6 Nov 2017 18:19:15 +0530 Subject: [PATCH 2/3] add subresources for custom resources --- .../app/autoscaling.go | 2 +- hack/.golint_failures | 1 - pkg/features/kube_features.go | 3 +- .../pkg/apis/apiextensions/types.go | 44 +- .../pkg/apis/apiextensions/v1beta1/types.go | 47 +- .../apiextensions/validation/validation.go | 92 +- .../customresource_discovery_controller.go | 21 + .../pkg/apiserver/customresource_handler.go | 232 ++++-- .../pkg/apiserver/validation/validation.go | 23 +- .../apiserver/validation/validation_test.go | 2 +- .../pkg/features/kube_features.go | 9 +- .../pkg/registry/customresource/etcd.go | 206 ++++- .../pkg/registry/customresource/etcd_test.go | 380 +++++++++ .../pkg/registry/customresource/registry.go | 104 +++ .../customresource/status_strategy.go | 62 ++ .../pkg/registry/customresource/strategy.go | 189 +++-- .../pkg/registry/customresource/validator.go | 241 ++++++ .../customresourcedefinition/strategy.go | 19 +- .../test/integration/basic_test.go | 22 +- .../test/integration/finalization_test.go | 2 +- .../test/integration/registration_test.go | 29 +- .../test/integration/subresources_test.go | 787 ++++++++++++++++++ .../test/integration/testserver/resources.go | 34 + .../test/integration/testserver/start.go | 39 +- .../test/integration/validation_test.go | 12 +- .../test/integration/yaml_test.go | 200 ++++- .../src/k8s.io/client-go/dynamic/client.go | 1 + .../src/k8s.io/client-go/scale/client_test.go | 2 +- 28 files changed, 2584 insertions(+), 221 deletions(-) create mode 100644 staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go create mode 100644 staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/registry.go create mode 100644 staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go create mode 100644 staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/validator.go create mode 100644 staging/src/k8s.io/apiextensions-apiserver/test/integration/subresources_test.go diff --git a/cmd/kube-controller-manager/app/autoscaling.go b/cmd/kube-controller-manager/app/autoscaling.go index 1f9532b73a4..9866990957e 100644 --- a/cmd/kube-controller-manager/app/autoscaling.go +++ b/cmd/kube-controller-manager/app/autoscaling.go @@ -24,7 +24,7 @@ import ( apimeta "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/discovery" - discocache "k8s.io/client-go/discovery/cached" // Saturday Night Fever + discocache "k8s.io/client-go/discovery/cached" "k8s.io/client-go/dynamic" "k8s.io/client-go/scale" "k8s.io/kubernetes/pkg/controller/podautoscaler" diff --git a/hack/.golint_failures b/hack/.golint_failures index c5f1aa2ccc4..90b0b224102 100644 --- a/hack/.golint_failures +++ b/hack/.golint_failures @@ -477,7 +477,6 @@ staging/src/k8s.io/apiextensions-apiserver/pkg/cmd/server staging/src/k8s.io/apiextensions-apiserver/pkg/controller/finalizer staging/src/k8s.io/apiextensions-apiserver/pkg/controller/status staging/src/k8s.io/apiextensions-apiserver/pkg/features -staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver staging/src/k8s.io/apimachinery/pkg/api/meta diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index e704f958de8..bc900a0b12e 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -297,7 +297,8 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS // inherited features from apiextensions-apiserver, relisted here to get a conflict if it is changed // unintentionally on either side: - apiextensionsfeatures.CustomResourceValidation: {Default: true, PreRelease: utilfeature.Beta}, + apiextensionsfeatures.CustomResourceValidation: {Default: true, PreRelease: utilfeature.Beta}, + apiextensionsfeatures.CustomResourceSubresources: {Default: false, PreRelease: utilfeature.Alpha}, // features that enable backwards compatibility but are scheduled to be removed ServiceProxyAllowExternalIPs: {Default: false, PreRelease: utilfeature.Deprecated}, diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/types.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/types.go index 880da65ce95..b7a20d9188f 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/types.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/types.go @@ -16,7 +16,9 @@ limitations under the License. package apiextensions -import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) // CustomResourceDefinitionSpec describes how a user wants their resource to appear type CustomResourceDefinitionSpec struct { @@ -30,6 +32,8 @@ type CustomResourceDefinitionSpec struct { Scope ResourceScope // Validation describes the validation methods for CustomResources Validation *CustomResourceValidation + // Subresources describes the subresources for CustomResources + Subresources *CustomResourceSubresources } // CustomResourceDefinitionNames indicates the names to serve this CustomResourceDefinition @@ -146,3 +150,41 @@ type CustomResourceValidation struct { // OpenAPIV3Schema is the OpenAPI v3 schema to be validated against. OpenAPIV3Schema *JSONSchemaProps } + +// CustomResourceSubresources defines the status and scale subresources for CustomResources. +type CustomResourceSubresources struct { + // Status denotes the status subresource for CustomResources + Status *CustomResourceSubresourceStatus + // Scale denotes the scale subresource for CustomResources + Scale *CustomResourceSubresourceScale +} + +// CustomResourceSubresourceStatus defines how to serve the status subresource for CustomResources. +// Status is represented by the `.status` JSON path inside of a CustomResource. When set, +// * exposes a /status subresource for the custom resource +// * PUT requests to the /status subresource take a custom resource object, and ignore changes to anything except the status stanza +// * PUT/POST/PATCH requests to the custom resource ignore changes to the status stanza +type CustomResourceSubresourceStatus struct{} + +// CustomResourceSubresourceScale defines how to serve the scale subresource for CustomResources. +type CustomResourceSubresourceScale struct { + // SpecReplicasPath defines the JSON path inside of a CustomResource that corresponds to Scale.Spec.Replicas. + // Only JSON paths without the array notation are allowed. + // Must be a JSON Path under .spec. + // If there is no value under the given path in the CustomResource, the /scale subresource will return an error on GET. + SpecReplicasPath string + // StatusReplicasPath defines the JSON path inside of a CustomResource that corresponds to Scale.Status.Replicas. + // Only JSON paths without the array notation are allowed. + // Must be a JSON Path under .status. + // If there is no value under the given path in the CustomResource, the status replica value in the /scale subresource + // will default to 0. + StatusReplicasPath string + // LabelSelectorPath defines the JSON path inside of a CustomResource that corresponds to Scale.Status.Selector. + // Only JSON paths without the array notation are allowed. + // Must be a JSON Path under .status. + // Must be set to work with HPA. + // If there is no value under the given path in the CustomResource, the status label selector value in the /scale + // subresource will default to the empty string. + // +optional + LabelSelectorPath *string +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types.go index 9ac37efe0fb..1afa457df21 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types.go @@ -16,7 +16,9 @@ limitations under the License. package v1beta1 -import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) // CustomResourceDefinitionSpec describes how a user wants their resource to appear type CustomResourceDefinitionSpec struct { @@ -31,6 +33,11 @@ type CustomResourceDefinitionSpec struct { // Validation describes the validation methods for CustomResources // +optional Validation *CustomResourceValidation `json:"validation,omitempty" protobuf:"bytes,5,opt,name=validation"` + // Subresources describes the subresources for CustomResources + // This field is alpha-level and should only be sent to servers that enable + // subresources via the CustomResourceSubresources feature gate. + // +optional + Subresources *CustomResourceSubresources `json:"subresources,omitempty" protobuf:"bytes,6,opt,name=subresources"` } // CustomResourceDefinitionNames indicates the names to serve this CustomResourceDefinition @@ -147,3 +154,41 @@ type CustomResourceValidation struct { // OpenAPIV3Schema is the OpenAPI v3 schema to be validated against. OpenAPIV3Schema *JSONSchemaProps `json:"openAPIV3Schema,omitempty" protobuf:"bytes,1,opt,name=openAPIV3Schema"` } + +// CustomResourceSubresources defines the status and scale subresources for CustomResources. +type CustomResourceSubresources struct { + // Status denotes the status subresource for CustomResources + Status *CustomResourceSubresourceStatus `json:"status,omitempty" protobuf:"bytes,1,opt,name=status"` + // Scale denotes the scale subresource for CustomResources + Scale *CustomResourceSubresourceScale `json:"scale,omitempty" protobuf:"bytes,2,opt,name=scale"` +} + +// CustomResourceSubresourceStatus defines how to serve the status subresource for CustomResources. +// Status is represented by the `.status` JSON path inside of a CustomResource. When set, +// * exposes a /status subresource for the custom resource +// * PUT requests to the /status subresource take a custom resource object, and ignore changes to anything except the status stanza +// * PUT/POST/PATCH requests to the custom resource ignore changes to the status stanza +type CustomResourceSubresourceStatus struct{} + +// CustomResourceSubresourceScale defines how to serve the scale subresource for CustomResources. +type CustomResourceSubresourceScale struct { + // SpecReplicasPath defines the JSON path inside of a CustomResource that corresponds to Scale.Spec.Replicas. + // Only JSON paths without the array notation are allowed. + // Must be a JSON Path under .spec. + // If there is no value under the given path in the CustomResource, the /scale subresource will return an error on GET. + SpecReplicasPath string `json:"specReplicasPath" protobuf:"bytes,1,name=specReplicasPath"` + // StatusReplicasPath defines the JSON path inside of a CustomResource that corresponds to Scale.Status.Replicas. + // Only JSON paths without the array notation are allowed. + // Must be a JSON Path under .status. + // If there is no value under the given path in the CustomResource, the status replica value in the /scale subresource + // will default to 0. + StatusReplicasPath string `json:"statusReplicasPath" protobuf:"bytes,2,opt,name=statusReplicasPath"` + // LabelSelectorPath defines the JSON path inside of a CustomResource that corresponds to Scale.Status.Selector. + // Only JSON paths without the array notation are allowed. + // Must be a JSON Path under .status. + // Must be set to work with HPA. + // If there is no value under the given path in the CustomResource, the status label selector value in the /scale + // subresource will default to the empty string. + // +optional + LabelSelectorPath *string `json:"labelSelectorPath,omitempty" protobuf:"bytes,3,opt,name=labelSelectorPath"` +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go index 4a85546452c..afec55cda9e 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go @@ -18,6 +18,7 @@ package validation import ( "fmt" + "reflect" "strings" genericvalidation "k8s.io/apimachinery/pkg/api/validation" @@ -107,7 +108,13 @@ func ValidateCustomResourceDefinitionSpec(spec *apiextensions.CustomResourceDefi if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceValidation) { allErrs = append(allErrs, ValidateCustomResourceDefinitionValidation(spec.Validation, fldPath.Child("validation"))...) } else if spec.Validation != nil { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("validation"), "disabled by feature-gate")) + allErrs = append(allErrs, field.Forbidden(fldPath.Child("validation"), "disabled by feature-gate CustomResourceValidation")) + } + + if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) { + allErrs = append(allErrs, ValidateCustomResourceDefinitionSubresources(spec.Subresources, fldPath.Child("subresources"))...) + } else if spec.Subresources != nil { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("subresources"), "disabled by feature-gate CustomResourceSubresources")) } return allErrs @@ -182,9 +189,27 @@ func ValidateCustomResourceDefinitionValidation(customResourceValidation *apiext return allErrs } - if customResourceValidation.OpenAPIV3Schema != nil { + if schema := customResourceValidation.OpenAPIV3Schema; schema != nil { + // if subresources are enabled, only properties is allowed inside the root schema + if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) { + v := reflect.ValueOf(schema).Elem() + fieldsPresent := 0 + + for i := 0; i < v.NumField(); i++ { + field := v.Field(i).Interface() + if !reflect.DeepEqual(field, reflect.Zero(reflect.TypeOf(field)).Interface()) { + fieldsPresent++ + } + } + + if fieldsPresent > 1 || (fieldsPresent == 1 && v.FieldByName("Properties").IsNil()) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("openAPIV3Schema"), *schema, fmt.Sprintf("if subresources for custom resources are enabled, only properties can be used at the root of the schema"))) + return allErrs + } + } + openAPIV3Schema := &specStandardValidatorV3{} - allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(customResourceValidation.OpenAPIV3Schema, fldPath.Child("openAPIV3Schema"), openAPIV3Schema)...) + allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(schema, fldPath.Child("openAPIV3Schema"), openAPIV3Schema)...) } // if validation passed otherwise, make sure we can actually construct a schema validator from this custom resource validation. @@ -326,3 +351,64 @@ func (v *specStandardValidatorV3) validate(schema *apiextensions.JSONSchemaProps return allErrs } + +// ValidateCustomResourceDefinitionSubresources statically validates +func ValidateCustomResourceDefinitionSubresources(subresources *apiextensions.CustomResourceSubresources, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if subresources == nil { + return allErrs + } + + if subresources.Scale != nil { + if len(subresources.Scale.SpecReplicasPath) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("scale.specReplicasPath"), "")) + } else { + // should be constrained json path under .spec + if errs := validateSimpleJSONPath(subresources.Scale.SpecReplicasPath, fldPath.Child("scale.specReplicasPath")); len(errs) > 0 { + allErrs = append(allErrs, errs...) + } else if !strings.HasPrefix(subresources.Scale.SpecReplicasPath, ".spec.") { + allErrs = append(allErrs, field.Invalid(fldPath.Child("scale.specReplicasPath"), subresources.Scale.SpecReplicasPath, "should be a json path under .spec")) + } + } + + if len(subresources.Scale.StatusReplicasPath) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("scale.statusReplicasPath"), "")) + } else { + // should be constrained json path under .status + if errs := validateSimpleJSONPath(subresources.Scale.StatusReplicasPath, fldPath.Child("scale.statusReplicasPath")); len(errs) > 0 { + allErrs = append(allErrs, errs...) + } else if !strings.HasPrefix(subresources.Scale.StatusReplicasPath, ".status.") { + allErrs = append(allErrs, field.Invalid(fldPath.Child("scale.statusReplicasPath"), subresources.Scale.StatusReplicasPath, "should be a json path under .status")) + } + } + + // if labelSelectorPath is present, it should be a constrained json path under .status + if subresources.Scale.LabelSelectorPath != nil && len(*subresources.Scale.LabelSelectorPath) > 0 { + if errs := validateSimpleJSONPath(*subresources.Scale.LabelSelectorPath, fldPath.Child("scale.labelSelectorPath")); len(errs) > 0 { + allErrs = append(allErrs, errs...) + } else if !strings.HasPrefix(*subresources.Scale.LabelSelectorPath, ".status.") { + allErrs = append(allErrs, field.Invalid(fldPath.Child("scale.labelSelectorPath"), subresources.Scale.LabelSelectorPath, "should be a json path under .status")) + } + } + } + + return allErrs +} + +func validateSimpleJSONPath(s string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + switch { + case len(s) == 0: + allErrs = append(allErrs, field.Invalid(fldPath, s, "must not be empty")) + case s[0] != '.': + allErrs = append(allErrs, field.Invalid(fldPath, s, "must be a simple json path starting with .")) + case s != ".": + if cs := strings.Split(s[1:], "."); len(cs) < 1 { + allErrs = append(allErrs, field.Invalid(fldPath, s, "must be a json path in the dot notation")) + } + } + + return allErrs +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_discovery_controller.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_discovery_controller.go index 85a2a27d097..c794517401d 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_discovery_controller.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_discovery_controller.go @@ -22,6 +22,7 @@ import ( "github.com/golang/glog" + autoscaling "k8s.io/api/autoscaling/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" @@ -117,6 +118,26 @@ func (c *DiscoveryController) sync(version schema.GroupVersion) error { Verbs: verbs, ShortNames: crd.Status.AcceptedNames.ShortNames, }) + + if crd.Spec.Subresources != nil && crd.Spec.Subresources.Status != nil { + apiResourcesForDiscovery = append(apiResourcesForDiscovery, metav1.APIResource{ + Name: crd.Status.AcceptedNames.Plural + "/status", + Namespaced: crd.Spec.Scope == apiextensions.NamespaceScoped, + Kind: crd.Status.AcceptedNames.Kind, + Verbs: metav1.Verbs([]string{"get", "patch", "update"}), + }) + } + + if crd.Spec.Subresources != nil && crd.Spec.Subresources.Scale != nil { + apiResourcesForDiscovery = append(apiResourcesForDiscovery, metav1.APIResource{ + Group: autoscaling.GroupName, + Version: "v1", + Kind: "Scale", + Name: crd.Status.AcceptedNames.Plural + "/scale", + Namespaced: crd.Spec.Scope == apiextensions.NamespaceScoped, + Verbs: metav1.Verbs([]string{"get", "patch", "update"}), + }) + } } if !foundGroup { diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go index 624565a1c83..dcc185c5f9b 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go @@ -25,6 +25,9 @@ import ( "sync/atomic" "time" + "github.com/go-openapi/spec" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/validate" "github.com/golang/glog" apiequality "k8s.io/apimachinery/pkg/api/equality" @@ -35,6 +38,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/runtime/serializer/json" "k8s.io/apimachinery/pkg/runtime/serializer/versioning" "k8s.io/apimachinery/pkg/types" @@ -47,7 +51,10 @@ import ( "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/storage/storagebackend" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/discovery" + "k8s.io/client-go/scale" + "k8s.io/client-go/scale/scheme/autoscalingv1" "k8s.io/client-go/tools/cache" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" @@ -55,6 +62,7 @@ import ( informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion" listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion" "k8s.io/apiextensions-apiserver/pkg/controller/finalizer" + apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features" "k8s.io/apiextensions-apiserver/pkg/registry/customresource" ) @@ -87,8 +95,11 @@ type crdInfo struct { spec *apiextensions.CustomResourceDefinitionSpec acceptedNames *apiextensions.CustomResourceDefinitionNames - storage *customresource.REST - requestScope handlers.RequestScope + storage customresource.CustomResourceStorage + + requestScope handlers.RequestScope + scaleRequestScope handlers.RequestScope + statusRequestScope handlers.RequestScope } // crdStorageMap goes from customresourcedefinition to its storage @@ -172,10 +183,6 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { r.delegate.ServeHTTP(w, req) return } - if len(requestInfo.Subresource) > 0 { - http.NotFound(w, req) - return - } terminating := apiextensions.IsCRDConditionTrue(crd, apiextensions.Terminating) @@ -185,61 +192,126 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - storage := crdInfo.storage - requestScope := crdInfo.requestScope - minRequestTimeout := 1 * time.Minute - verb := strings.ToUpper(requestInfo.Verb) resource := requestInfo.Resource subresource := requestInfo.Subresource scope := metrics.CleanScope(requestInfo) + supportedTypes := []string{ + string(types.JSONPatchType), + string(types.MergePatchType), + } var handler http.HandlerFunc + switch { + case subresource == "status" && crd.Spec.Subresources != nil && crd.Spec.Subresources.Status != nil: + handler = r.serveStatus(w, req, requestInfo, crdInfo, terminating, supportedTypes) + case subresource == "scale" && crd.Spec.Subresources != nil && crd.Spec.Subresources.Scale != nil: + handler = r.serveScale(w, req, requestInfo, crdInfo, terminating, supportedTypes) + case len(subresource) == 0: + handler = r.serveResource(w, req, requestInfo, crdInfo, terminating, supportedTypes) + default: + http.Error(w, "the server could not find the requested resource", http.StatusNotFound) + } + + if handler != nil { + handler = metrics.InstrumentHandlerFunc(verb, resource, subresource, scope, handler) + handler(w, req) + return + } +} + +func (r *crdHandler) serveResource(w http.ResponseWriter, req *http.Request, requestInfo *apirequest.RequestInfo, crdInfo *crdInfo, terminating bool, supportedTypes []string) http.HandlerFunc { + requestScope := crdInfo.requestScope + storage := crdInfo.storage.CustomResource + minRequestTimeout := 1 * time.Minute switch requestInfo.Verb { case "get": - handler = handlers.GetResource(storage, storage, requestScope) + return handlers.GetResource(storage, storage, requestScope) case "list": forceWatch := false - handler = handlers.ListResource(storage, storage, requestScope, forceWatch, minRequestTimeout) + return handlers.ListResource(storage, storage, requestScope, forceWatch, minRequestTimeout) case "watch": forceWatch := true - handler = handlers.ListResource(storage, storage, requestScope, forceWatch, minRequestTimeout) + return handlers.ListResource(storage, storage, requestScope, forceWatch, minRequestTimeout) case "create": if terminating { http.Error(w, fmt.Sprintf("%v not allowed while CustomResourceDefinition is terminating", requestInfo.Verb), http.StatusMethodNotAllowed) - return + return nil } - handler = handlers.CreateResource(storage, requestScope, discovery.NewUnstructuredObjectTyper(nil), r.admission) + return handlers.CreateResource(storage, requestScope, discovery.NewUnstructuredObjectTyper(nil), r.admission) case "update": if terminating { http.Error(w, fmt.Sprintf("%v not allowed while CustomResourceDefinition is terminating", requestInfo.Verb), http.StatusMethodNotAllowed) - return + return nil } - handler = handlers.UpdateResource(storage, requestScope, discovery.NewUnstructuredObjectTyper(nil), r.admission) + return handlers.UpdateResource(storage, requestScope, discovery.NewUnstructuredObjectTyper(nil), r.admission) case "patch": if terminating { http.Error(w, fmt.Sprintf("%v not allowed while CustomResourceDefinition is terminating", requestInfo.Verb), http.StatusMethodNotAllowed) - return + return nil } - supportedTypes := []string{ - string(types.JSONPatchType), - string(types.MergePatchType), - } - handler = handlers.PatchResource(storage, requestScope, r.admission, unstructured.UnstructuredObjectConverter{}, supportedTypes) + return handlers.PatchResource(storage, requestScope, r.admission, unstructured.UnstructuredObjectConverter{}, supportedTypes) case "delete": allowsOptions := true - handler = handlers.DeleteResource(storage, allowsOptions, requestScope, r.admission) + return handlers.DeleteResource(storage, allowsOptions, requestScope, r.admission) case "deletecollection": checkBody := true - handler = handlers.DeleteCollection(storage, checkBody, requestScope, r.admission) + return handlers.DeleteCollection(storage, checkBody, requestScope, r.admission) default: http.Error(w, fmt.Sprintf("unhandled verb %q", requestInfo.Verb), http.StatusMethodNotAllowed) - return + return nil + } +} + +func (r *crdHandler) serveStatus(w http.ResponseWriter, req *http.Request, requestInfo *apirequest.RequestInfo, crdInfo *crdInfo, terminating bool, supportedTypes []string) http.HandlerFunc { + requestScope := crdInfo.statusRequestScope + storage := crdInfo.storage.Status + + switch requestInfo.Verb { + case "get": + return handlers.GetResource(storage, nil, requestScope) + case "update": + if terminating { + http.Error(w, fmt.Sprintf("%v not allowed while CustomResourceDefinition is terminating", requestInfo.Verb), http.StatusMethodNotAllowed) + return nil + } + return handlers.UpdateResource(storage, requestScope, discovery.NewUnstructuredObjectTyper(nil), r.admission) + case "patch": + if terminating { + http.Error(w, fmt.Sprintf("%v not allowed while CustomResourceDefinition is terminating", requestInfo.Verb), http.StatusMethodNotAllowed) + return nil + } + return handlers.PatchResource(storage, requestScope, r.admission, unstructured.UnstructuredObjectConverter{}, supportedTypes) + default: + http.Error(w, fmt.Sprintf("unhandled verb %q", requestInfo.Verb), http.StatusMethodNotAllowed) + return nil + } +} + +func (r *crdHandler) serveScale(w http.ResponseWriter, req *http.Request, requestInfo *apirequest.RequestInfo, crdInfo *crdInfo, terminating bool, supportedTypes []string) http.HandlerFunc { + requestScope := crdInfo.scaleRequestScope + storage := crdInfo.storage.Scale + + switch requestInfo.Verb { + case "get": + return handlers.GetResource(storage, nil, requestScope) + case "update": + if terminating { + http.Error(w, fmt.Sprintf("%v not allowed while CustomResourceDefinition is terminating", requestInfo.Verb), http.StatusMethodNotAllowed) + return nil + } + return handlers.UpdateResource(storage, requestScope, discovery.NewUnstructuredObjectTyper(nil), r.admission) + case "patch": + if terminating { + http.Error(w, fmt.Sprintf("%v not allowed while CustomResourceDefinition is terminating", requestInfo.Verb), http.StatusMethodNotAllowed) + return nil + } + return handlers.PatchResource(storage, requestScope, r.admission, unstructured.UnstructuredObjectConverter{}, supportedTypes) + default: + http.Error(w, fmt.Sprintf("unhandled verb %q", requestInfo.Verb), http.StatusMethodNotAllowed) + return nil } - handler = metrics.InstrumentHandlerFunc(verb, resource, subresource, scope, handler) - handler(w, req) - return } func (r *crdHandler) updateCustomResourceDefinition(oldObj, newObj interface{}) { @@ -265,7 +337,8 @@ func (r *crdHandler) updateCustomResourceDefinition(oldObj, newObj interface{}) // as it is used without locking elsewhere. storageMap2 := storageMap.clone() if oldInfo, ok := storageMap2[types.UID(oldCRD.UID)]; ok { - oldInfo.storage.DestroyFunc() + // destroy only the main storage. Those for the subresources share cacher and etcd clients. + oldInfo.storage.CustomResource.DestroyFunc() delete(storageMap2, types.UID(oldCRD.UID)) } @@ -297,7 +370,8 @@ func (r *crdHandler) removeDeadStorage() { } if !found { glog.V(4).Infof("Removing dead CRD storage for %v", s.requestScope.Resource) - s.storage.DestroyFunc() + // destroy only the main storage. Those for the subresources share cacher and etcd clients. + s.storage.CustomResource.DestroyFunc() delete(storageMap2, uid) } } @@ -311,7 +385,7 @@ func (r *crdHandler) GetCustomResourceListerCollectionDeleter(crd *apiextensions if err != nil { return nil, err } - return info.storage, nil + return info.storage.CustomResource, nil } func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResourceDefinition) (*crdInfo, error) { @@ -340,9 +414,9 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource parameterCodec := runtime.NewParameterCodec(parameterScheme) kind := schema.GroupVersionKind{Group: crd.Spec.Group, Version: crd.Spec.Version, Kind: crd.Status.AcceptedNames.Kind} - typer := unstructuredObjectTyper{ - delegate: parameterScheme, - unstructuredTyper: discovery.NewUnstructuredObjectTyper(nil), + typer := UnstructuredObjectTyper{ + Delegate: parameterScheme, + UnstructuredTyper: discovery.NewUnstructuredObjectTyper(nil), } creator := unstructuredCreator{} @@ -351,7 +425,29 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource return nil, err } - storage := customresource.NewREST( + var statusSpec *apiextensions.CustomResourceSubresourceStatus + var statusValidator *validate.SchemaValidator + if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && crd.Spec.Subresources != nil && crd.Spec.Subresources.Status != nil { + statusSpec = crd.Spec.Subresources.Status + + // for the status subresource, validate only against the status schema + if crd.Spec.Validation != nil && crd.Spec.Validation.OpenAPIV3Schema != nil && crd.Spec.Validation.OpenAPIV3Schema.Properties != nil { + if statusSchema, ok := crd.Spec.Validation.OpenAPIV3Schema.Properties["status"]; ok { + openapiSchema := &spec.Schema{} + if err := apiservervalidation.ConvertJSONSchemaProps(&statusSchema, openapiSchema); err != nil { + return nil, err + } + statusValidator = validate.NewSchemaValidator(openapiSchema, nil, "", strfmt.Default) + } + } + } + + var scaleSpec *apiextensions.CustomResourceSubresourceScale + if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && crd.Spec.Subresources != nil && crd.Spec.Subresources.Scale != nil { + scaleSpec = crd.Spec.Subresources.Scale + } + + customResourceStorage := customresource.NewStorage( schema.GroupResource{Group: crd.Spec.Group, Resource: crd.Status.AcceptedNames.Plural}, schema.GroupVersionKind{Group: crd.Spec.Group, Version: crd.Spec.Version, Kind: crd.Status.AcceptedNames.ListKind}, customresource.NewStrategy( @@ -359,6 +455,9 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource crd.Spec.Scope == apiextensions.NamespaceScoped, kind, validator, + statusValidator, + statusSpec, + scaleSpec, ), r.restOptionsGetter, ) @@ -373,12 +472,15 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource clusterScoped := crd.Spec.Scope == apiextensions.ClusterScoped + var ctxFn handlers.ContextFunc + ctxFn = func(req *http.Request) apirequest.Context { + ret, _ := r.requestContextMapper.Get(req) + return ret + } + requestScope := handlers.RequestScope{ Namer: handlers.ContextBasedNaming{ - GetContext: func(req *http.Request) apirequest.Context { - ret, _ := r.requestContextMapper.Get(req) - return ret - }, + GetContext: ctxFn, SelfLinker: meta.NewAccessor(), ClusterScoped: clusterScoped, SelfLinkPathPrefix: selfLinkPrefix, @@ -400,9 +502,8 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource Typer: typer, UnsafeConvertor: unstructured.UnstructuredObjectConverter{}, - Resource: schema.GroupVersionResource{Group: crd.Spec.Group, Version: crd.Spec.Version, Resource: crd.Status.AcceptedNames.Plural}, - Kind: kind, - Subresource: "", + Resource: schema.GroupVersionResource{Group: crd.Spec.Group, Version: crd.Spec.Version, Resource: crd.Status.AcceptedNames.Plural}, + Kind: kind, MetaGroupVersion: metav1.SchemeGroupVersion, } @@ -411,8 +512,33 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource spec: &crd.Spec, acceptedNames: &crd.Status.AcceptedNames, - storage: storage, - requestScope: requestScope, + storage: customResourceStorage, + requestScope: requestScope, + scaleRequestScope: requestScope, // shallow copy + statusRequestScope: requestScope, // shallow copy + } + + // override scaleSpec subresource values + scaleConverter := scale.NewScaleConverter() + ret.scaleRequestScope.Subresource = "scale" + ret.scaleRequestScope.Serializer = serializer.NewCodecFactory(scaleConverter.Scheme()) + ret.scaleRequestScope.Kind = autoscalingv1.SchemeGroupVersion.WithKind("Scale") + ret.scaleRequestScope.Namer = handlers.ContextBasedNaming{ + GetContext: ctxFn, + SelfLinker: meta.NewAccessor(), + ClusterScoped: clusterScoped, + SelfLinkPathPrefix: selfLinkPrefix, + SelfLinkPathSuffix: "/scale", + } + + // override status subresource values + ret.statusRequestScope.Subresource = "status" + ret.statusRequestScope.Namer = handlers.ContextBasedNaming{ + GetContext: ctxFn, + SelfLinker: meta.NewAccessor(), + ClusterScoped: clusterScoped, + SelfLinkPathPrefix: selfLinkPrefix, + SelfLinkPathSuffix: "/status", } // Copy because we cannot write to storageMap without a race @@ -477,21 +603,21 @@ func (s unstructuredNegotiatedSerializer) DecoderToVersion(decoder runtime.Decod return versioning.NewDefaultingCodecForScheme(Scheme, nil, decoder, nil, gv) } -type unstructuredObjectTyper struct { - delegate runtime.ObjectTyper - unstructuredTyper runtime.ObjectTyper +type UnstructuredObjectTyper struct { + Delegate runtime.ObjectTyper + UnstructuredTyper runtime.ObjectTyper } -func (t unstructuredObjectTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) { +func (t UnstructuredObjectTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) { // Delegate for things other than Unstructured. if _, ok := obj.(runtime.Unstructured); !ok { - return t.delegate.ObjectKinds(obj) + return t.Delegate.ObjectKinds(obj) } - return t.unstructuredTyper.ObjectKinds(obj) + return t.UnstructuredTyper.ObjectKinds(obj) } -func (t unstructuredObjectTyper) Recognizes(gvk schema.GroupVersionKind) bool { - return t.delegate.Recognizes(gvk) || t.unstructuredTyper.Recognizes(gvk) +func (t UnstructuredObjectTyper) Recognizes(gvk schema.GroupVersionKind) bool { + return t.Delegate.Recognizes(gvk) || t.UnstructuredTyper.Recognizes(gvk) } type unstructuredCreator struct{} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation.go index e39ac3e6d68..de8e1af04d6 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation.go @@ -29,7 +29,7 @@ func NewSchemaValidator(customResourceValidation *apiextensions.CustomResourceVa // Convert CRD schema to openapi schema openapiSchema := &spec.Schema{} if customResourceValidation != nil { - if err := convertJSONSchemaProps(customResourceValidation.OpenAPIV3Schema, openapiSchema); err != nil { + if err := ConvertJSONSchemaProps(customResourceValidation.OpenAPIV3Schema, openapiSchema); err != nil { return nil, err } } @@ -39,6 +39,10 @@ func NewSchemaValidator(customResourceValidation *apiextensions.CustomResourceVa // ValidateCustomResource validates the Custom Resource against the schema in the CustomResourceDefinition. // CustomResource is a JSON data structure. func ValidateCustomResource(customResource interface{}, validator *validate.SchemaValidator) error { + if validator == nil { + return nil + } + result := validator.Validate(customResource) if result.AsError() != nil { return result.AsError() @@ -46,7 +50,8 @@ func ValidateCustomResource(customResource interface{}, validator *validate.Sche return nil } -func convertJSONSchemaProps(in *apiextensions.JSONSchemaProps, out *spec.Schema) error { +// ConvertJSONSchemaProps converts the schema from apiextensions.JSONSchemaPropos to go-openapi/spec.Schema +func ConvertJSONSchemaProps(in *apiextensions.JSONSchemaProps, out *spec.Schema) error { if in == nil { return nil } @@ -99,7 +104,7 @@ func convertJSONSchemaProps(in *apiextensions.JSONSchemaProps, out *spec.Schema) if in.Not != nil { in, out := &in.Not, &out.Not *out = new(spec.Schema) - if err := convertJSONSchemaProps(*in, *out); err != nil { + if err := ConvertJSONSchemaProps(*in, *out); err != nil { return err } } @@ -176,7 +181,7 @@ func convertSliceOfJSONSchemaProps(in *[]apiextensions.JSONSchemaProps, out *[]s if in != nil { for _, jsonSchemaProps := range *in { schema := spec.Schema{} - if err := convertJSONSchemaProps(&jsonSchemaProps, &schema); err != nil { + if err := ConvertJSONSchemaProps(&jsonSchemaProps, &schema); err != nil { return err } *out = append(*out, schema) @@ -190,7 +195,7 @@ func convertMapOfJSONSchemaProps(in map[string]apiextensions.JSONSchemaProps) (m if len(in) != 0 { for k, jsonSchemaProps := range in { schema := spec.Schema{} - if err := convertJSONSchemaProps(&jsonSchemaProps, &schema); err != nil { + if err := ConvertJSONSchemaProps(&jsonSchemaProps, &schema); err != nil { return nil, err } out[k] = schema @@ -203,7 +208,7 @@ func convertJSONSchemaPropsOrArray(in *apiextensions.JSONSchemaPropsOrArray, out if in.Schema != nil { in, out := &in.Schema, &out.Schema *out = new(spec.Schema) - if err := convertJSONSchemaProps(*in, *out); err != nil { + if err := ConvertJSONSchemaProps(*in, *out); err != nil { return err } } @@ -211,7 +216,7 @@ func convertJSONSchemaPropsOrArray(in *apiextensions.JSONSchemaPropsOrArray, out in, out := &in.JSONSchemas, &out.Schemas *out = make([]spec.Schema, len(*in)) for i := range *in { - if err := convertJSONSchemaProps(&(*in)[i], &(*out)[i]); err != nil { + if err := ConvertJSONSchemaProps(&(*in)[i], &(*out)[i]); err != nil { return err } } @@ -224,7 +229,7 @@ func convertJSONSchemaPropsorBool(in *apiextensions.JSONSchemaPropsOrBool, out * if in.Schema != nil { in, out := &in.Schema, &out.Schema *out = new(spec.Schema) - if err := convertJSONSchemaProps(*in, *out); err != nil { + if err := ConvertJSONSchemaProps(*in, *out); err != nil { return err } } @@ -236,7 +241,7 @@ func convertJSONSchemaPropsOrStringArray(in *apiextensions.JSONSchemaPropsOrStri if in.Schema != nil { in, out := &in.Schema, &out.Schema *out = new(spec.Schema) - if err := convertJSONSchemaProps(*in, *out); err != nil { + if err := ConvertJSONSchemaProps(*in, *out); err != nil { return err } } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation_test.go index a64c48f2d1f..3d9c50c840c 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation_test.go @@ -58,7 +58,7 @@ func TestRoundTrip(t *testing.T) { // internal -> go-openapi openAPITypes := &spec.Schema{} - if err := convertJSONSchemaProps(internal, openAPITypes); err != nil { + if err := ConvertJSONSchemaProps(internal, openAPITypes); err != nil { t.Fatal(err) } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/features/kube_features.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/features/kube_features.go index 80b37bf7bce..b80e80ac361 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/features/kube_features.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/features/kube_features.go @@ -33,6 +33,12 @@ const ( // // CustomResourceValidation is a list of validation methods for CustomResources CustomResourceValidation utilfeature.Feature = "CustomResourceValidation" + + // owner: @sttts, @nikhita + // alpha: v1.10 + // + // CustomResourceSubresources defines the subresources for CustomResources + CustomResourceSubresources utilfeature.Feature = "CustomResourceSubresources" ) func init() { @@ -43,5 +49,6 @@ func init() { // To add a new feature, define a key for it above and add it here. The features will be // available throughout Kubernetes binaries. var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureSpec{ - CustomResourceValidation: {Default: true, PreRelease: utilfeature.Beta}, + CustomResourceValidation: {Default: true, PreRelease: utilfeature.Beta}, + CustomResourceSubresources: {Default: false, PreRelease: utilfeature.Alpha}, } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go index 0cbf3b1122e..aef806a7796 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go @@ -17,20 +17,64 @@ limitations under the License. package customresource import ( + "fmt" + "strings" + + autoscalingv1 "k8s.io/api/autoscaling/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" + "k8s.io/apiserver/pkg/registry/rest" ) +// CustomResourceStorage includes dummy storage for CustomResources, and their Status and Scale subresources. +type CustomResourceStorage struct { + CustomResource *REST + Status *StatusREST + Scale *ScaleREST +} + +func NewStorage(resource schema.GroupResource, listKind schema.GroupVersionKind, strategy customResourceStrategy, optsGetter generic.RESTOptionsGetter) CustomResourceStorage { + customResourceREST, customResourceStatusREST := newREST(resource, listKind, strategy, optsGetter) + customResourceRegistry := NewRegistry(customResourceREST) + + s := CustomResourceStorage{ + CustomResource: customResourceREST, + } + + if strategy.status != nil { + s.Status = customResourceStatusREST + } + + if scale := strategy.scale; scale != nil { + var labelSelectorPath string + if scale.LabelSelectorPath != nil { + labelSelectorPath = *scale.LabelSelectorPath + } + + s.Scale = &ScaleREST{ + registry: customResourceRegistry, + specReplicasPath: scale.SpecReplicasPath, + statusReplicasPath: scale.StatusReplicasPath, + labelSelectorPath: labelSelectorPath, + } + } + + return s +} + // REST implements a RESTStorage for API services against etcd type REST struct { *genericregistry.Store } -// NewREST returns a RESTStorage object that will work against API services. -func NewREST(resource schema.GroupResource, listKind schema.GroupVersionKind, strategy customResourceDefinitionStorageStrategy, optsGetter generic.RESTOptionsGetter) *REST { +// newREST returns a RESTStorage object that will work against API services. +func newREST(resource schema.GroupResource, listKind schema.GroupVersionKind, strategy customResourceStrategy, optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST) { store := &genericregistry.Store{ NewFunc: func() runtime.Object { return &unstructured.Unstructured{} }, NewListFunc: func() runtime.Object { @@ -50,5 +94,161 @@ func NewREST(resource schema.GroupResource, listKind schema.GroupVersionKind, st if err := store.CompleteWithOptions(options); err != nil { panic(err) // TODO: Propagate error up } - return &REST{store} + + statusStore := *store + statusStore.UpdateStrategy = NewStatusStrategy(strategy) + return &REST{store}, &StatusREST{store: &statusStore} +} + +// StatusREST implements the REST endpoint for changing the status of a CustomResource +type StatusREST struct { + store *genericregistry.Store +} + +func (r *StatusREST) New() runtime.Object { + return &unstructured.Unstructured{} +} + +// Get retrieves the object from the storage. It is required to support Patch. +func (r *StatusREST) Get(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + return r.store.Get(ctx, name, options) +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation) +} + +type ScaleREST struct { + registry Registry + specReplicasPath string + statusReplicasPath string + labelSelectorPath string +} + +// ScaleREST implements Patcher +var _ = rest.Patcher(&ScaleREST{}) +var _ = rest.GroupVersionKindProvider(&ScaleREST{}) + +func (r *ScaleREST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind { + return autoscalingv1.SchemeGroupVersion.WithKind("Scale") +} + +// New creates a new Scale object +func (r *ScaleREST) New() runtime.Object { + return &autoscalingv1.Scale{} +} + +func (r *ScaleREST) Get(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + cr, err := r.registry.GetCustomResource(ctx, name, options) + if err != nil { + return nil, err + } + + scaleObject, replicasFound, err := scaleFromCustomResource(cr, r.specReplicasPath, r.statusReplicasPath, r.labelSelectorPath) + if err != nil { + return nil, err + } + if !replicasFound { + return nil, apierrors.NewInternalError(fmt.Errorf("the spec replicas field %q does not exist", r.specReplicasPath)) + } + return scaleObject, err +} + +func (r *ScaleREST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (runtime.Object, bool, error) { + cr, err := r.registry.GetCustomResource(ctx, name, &metav1.GetOptions{}) + if err != nil { + return nil, false, err + } + + const invalidSpecReplicas = -2147483648 // smallest int32 + oldScale, replicasFound, err := scaleFromCustomResource(cr, r.specReplicasPath, r.statusReplicasPath, r.labelSelectorPath) + if err != nil { + return nil, false, err + } + if !replicasFound { + oldScale.Spec.Replicas = invalidSpecReplicas // signal that this was not set before + } + + obj, err := objInfo.UpdatedObject(ctx, oldScale) + if err != nil { + return nil, false, err + } + if obj == nil { + return nil, false, apierrors.NewBadRequest(fmt.Sprintf("nil update passed to Scale")) + } + + scale, ok := obj.(*autoscalingv1.Scale) + if !ok { + return nil, false, apierrors.NewBadRequest(fmt.Sprintf("wrong object passed to Scale update: %v", obj)) + } + + if scale.Spec.Replicas == invalidSpecReplicas { + return nil, false, apierrors.NewBadRequest(fmt.Sprintf("the spec replicas field %q cannot be empty", r.specReplicasPath)) + } + + specReplicasPath := strings.TrimPrefix(r.specReplicasPath, ".") // ignore leading period + if err = unstructured.SetNestedField(cr.Object, int64(scale.Spec.Replicas), strings.Split(specReplicasPath, ".")...); err != nil { + return nil, false, err + } + cr.SetResourceVersion(scale.ResourceVersion) + + cr, err = r.registry.UpdateCustomResource(ctx, cr, createValidation, updateValidation) + if err != nil { + return nil, false, err + } + + newScale, _, err := scaleFromCustomResource(cr, r.specReplicasPath, r.statusReplicasPath, r.labelSelectorPath) + if err != nil { + return nil, false, apierrors.NewBadRequest(err.Error()) + } + return newScale, false, err +} + +// scaleFromCustomResource returns a scale subresource for a customresource and a bool signalling wether +// the specReplicas value was found. +func scaleFromCustomResource(cr *unstructured.Unstructured, specReplicasPath, statusReplicasPath, labelSelectorPath string) (*autoscalingv1.Scale, bool, error) { + specReplicasPath = strings.TrimPrefix(specReplicasPath, ".") // ignore leading period + specReplicas, foundSpecReplicas, err := unstructured.NestedInt64(cr.UnstructuredContent(), strings.Split(specReplicasPath, ".")...) + if err != nil { + return nil, false, err + } else if !foundSpecReplicas { + specReplicas = 0 + } + + statusReplicasPath = strings.TrimPrefix(statusReplicasPath, ".") // ignore leading period + statusReplicas, found, err := unstructured.NestedInt64(cr.UnstructuredContent(), strings.Split(statusReplicasPath, ".")...) + if err != nil { + return nil, false, err + } else if !found { + statusReplicas = 0 + } + + var labelSelector string + if len(labelSelectorPath) > 0 { + labelSelectorPath = strings.TrimPrefix(labelSelectorPath, ".") // ignore leading period + labelSelector, found, err = unstructured.NestedString(cr.UnstructuredContent(), strings.Split(labelSelectorPath, ".")...) + if err != nil { + return nil, false, err + } + } + + scale := &autoscalingv1.Scale{ + ObjectMeta: metav1.ObjectMeta{ + Name: cr.GetName(), + Namespace: cr.GetNamespace(), + UID: cr.GetUID(), + ResourceVersion: cr.GetResourceVersion(), + CreationTimestamp: cr.GetCreationTimestamp(), + }, + Spec: autoscalingv1.ScaleSpec{ + Replicas: int32(specReplicas), + }, + Status: autoscalingv1.ScaleStatus{ + Replicas: int32(statusReplicas), + Selector: labelSelector, + }, + } + + return scale, foundSpecReplicas, nil } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go new file mode 100644 index 00000000000..6b4bb02e668 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go @@ -0,0 +1,380 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package customresource_test + +import ( + "io" + "strings" + "testing" + + autoscalingv1 "k8s.io/api/autoscaling/v1" + apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/diff" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/generic" + registrytest "k8s.io/apiserver/pkg/registry/generic/testing" + "k8s.io/apiserver/pkg/registry/rest" + etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" + "k8s.io/client-go/discovery" + + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + "k8s.io/apiextensions-apiserver/pkg/apiserver" + "k8s.io/apiextensions-apiserver/pkg/registry/customresource" +) + +func newStorage(t *testing.T) (customresource.CustomResourceStorage, *etcdtesting.EtcdTestServer) { + server, etcdStorage := etcdtesting.NewUnsecuredEtcd3TestClientServer(t) + etcdStorage.Codec = unstructuredJsonCodec{} + restOptions := generic.RESTOptions{StorageConfig: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1, ResourcePrefix: "noxus"} + + parameterScheme := runtime.NewScheme() + parameterScheme.AddUnversionedTypes(schema.GroupVersion{Group: "mygroup.example.com", Version: "v1beta1"}, + &metav1.ListOptions{}, + &metav1.ExportOptions{}, + &metav1.GetOptions{}, + &metav1.DeleteOptions{}, + ) + + typer := apiserver.UnstructuredObjectTyper{ + Delegate: parameterScheme, + UnstructuredTyper: discovery.NewUnstructuredObjectTyper(nil), + } + + kind := schema.GroupVersionKind{Group: "mygroup.example.com", Version: "v1beta1", Kind: "Noxu"} + + labelSelectorPath := ".status.labelSelector" + scale := &apiextensions.CustomResourceSubresourceScale{ + SpecReplicasPath: ".spec.replicas", + StatusReplicasPath: ".status.replicas", + LabelSelectorPath: &labelSelectorPath, + } + + status := &apiextensions.CustomResourceSubresourceStatus{} + + storage := customresource.NewStorage( + schema.GroupResource{Group: "mygroup.example.com", Resource: "noxus"}, + schema.GroupVersionKind{Group: "mygroup.example.com", Version: "v1beta1", Kind: "NoxuItemList"}, + customresource.NewStrategy( + typer, + true, + kind, + nil, + nil, + status, + scale, + ), + restOptions, + ) + + return storage, server +} + +// createCustomResource is a helper function that returns a CustomResource with the updated resource version. +func createCustomResource(storage *customresource.REST, cr unstructured.Unstructured, t *testing.T) (unstructured.Unstructured, error) { + ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), cr.GetNamespace()) + obj, err := storage.Create(ctx, &cr, rest.ValidateAllObjectFunc, false) + if err != nil { + t.Errorf("Failed to create CustomResource, %v", err) + } + newCR := obj.(*unstructured.Unstructured) + return *newCR, nil +} + +func validNewCustomResource() *unstructured.Unstructured { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "mygroup.example.com/v1beta1", + "kind": "Noxu", + "metadata": map[string]interface{}{ + "namespace": "default", + "name": "foo", + }, + "spec": map[string]interface{}{ + "replicas": int64(7), + }, + }, + } +} + +var validCustomResource = *validNewCustomResource() + +func TestCreate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + defer storage.CustomResource.Store.DestroyFunc() + test := registrytest.New(t, storage.CustomResource.Store) + cr := validNewCustomResource() + cr.SetNamespace("") + test.TestCreate( + cr, + ) +} + +func TestGet(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + defer storage.CustomResource.Store.DestroyFunc() + test := registrytest.New(t, storage.CustomResource.Store) + test.TestGet(validNewCustomResource()) +} + +func TestList(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + defer storage.CustomResource.Store.DestroyFunc() + test := registrytest.New(t, storage.CustomResource.Store) + test.TestList(validNewCustomResource()) +} + +func TestDelete(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + defer storage.CustomResource.Store.DestroyFunc() + test := registrytest.New(t, storage.CustomResource.Store) + test.TestDelete(validNewCustomResource()) +} + +func TestStatusUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + defer storage.CustomResource.Store.DestroyFunc() + ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault) + key := "/noxus/" + metav1.NamespaceDefault + "/foo" + validCustomResource := validNewCustomResource() + if err := storage.CustomResource.Storage.Create(ctx, key, validCustomResource, nil, 0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + gottenObj, err := storage.CustomResource.Get(ctx, "foo", &metav1.GetOptions{}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + update := gottenObj.(*unstructured.Unstructured) + updateContent := update.Object + updateContent["status"] = map[string]interface{}{ + "replicas": int64(7), + } + + if _, _, err := storage.Status.Update(ctx, update.GetName(), rest.DefaultUpdatedObjectInfo(update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + obj, err := storage.CustomResource.Get(ctx, "foo", &metav1.GetOptions{}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + cr, ok := obj.(*unstructured.Unstructured) + if !ok { + t.Fatal("unexpected error: custom resource should be of type Unstructured") + } + content := cr.UnstructuredContent() + + spec := content["spec"].(map[string]interface{}) + status := content["status"].(map[string]interface{}) + + if spec["replicas"].(int64) != 7 { + t.Errorf("we expected .spec.replicas to not be updated but it was updated to %v", spec["replicas"].(int64)) + } + if status["replicas"].(int64) != 7 { + t.Errorf("we expected .status.replicas to be updated to %d but it was %v", 7, status["replicas"].(int64)) + } +} + +func TestScaleGet(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + defer storage.CustomResource.Store.DestroyFunc() + + name := "foo" + + var cr unstructured.Unstructured + ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault) + key := "/noxus/" + metav1.NamespaceDefault + "/" + name + if err := storage.CustomResource.Storage.Create(ctx, key, &validCustomResource, &cr, 0); err != nil { + t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, validCustomResource, err) + } + + want := &autoscalingv1.Scale{ + ObjectMeta: metav1.ObjectMeta{ + Name: cr.GetName(), + Namespace: metav1.NamespaceDefault, + UID: cr.GetUID(), + ResourceVersion: cr.GetResourceVersion(), + CreationTimestamp: cr.GetCreationTimestamp(), + }, + Spec: autoscalingv1.ScaleSpec{ + Replicas: int32(7), + }, + } + + obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{}) + if err != nil { + t.Fatalf("error fetching scale for %s: %v", name, err) + } + + got := obj.(*autoscalingv1.Scale) + if !apiequality.Semantic.DeepEqual(got, want) { + t.Errorf("unexpected scale: %s", diff.ObjectDiff(got, want)) + } +} + +func TestScaleGetWithoutSpecReplicas(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + defer storage.CustomResource.Store.DestroyFunc() + + name := "foo" + + var cr unstructured.Unstructured + ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault) + key := "/noxus/" + metav1.NamespaceDefault + "/" + name + withoutSpecReplicas := validCustomResource.DeepCopy() + unstructured.RemoveNestedField(withoutSpecReplicas.Object, "spec", "replicas") + if err := storage.CustomResource.Storage.Create(ctx, key, withoutSpecReplicas, &cr, 0); err != nil { + t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, withoutSpecReplicas, err) + } + + _, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{}) + if err == nil { + t.Fatalf("error expected for %s", name) + } + if expected := `the spec replicas field ".spec.replicas" does not exist`; !strings.Contains(err.Error(), expected) { + t.Fatalf("expected error string %q, got: %v", expected, err) + } +} + +func TestScaleUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + defer storage.CustomResource.Store.DestroyFunc() + + name := "foo" + + var cr unstructured.Unstructured + ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault) + key := "/noxus/" + metav1.NamespaceDefault + "/" + name + if err := storage.CustomResource.Storage.Create(ctx, key, &validCustomResource, &cr, 0); err != nil { + t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, validCustomResource, err) + } + + obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{}) + if err != nil { + t.Fatalf("error fetching scale for %s: %v", name, err) + } + scale, ok := obj.(*autoscalingv1.Scale) + if !ok { + t.Fatalf("%v is not of the type autoscalingv1.Scale", scale) + } + + replicas := 12 + update := autoscalingv1.Scale{ + ObjectMeta: scale.ObjectMeta, + Spec: autoscalingv1.ScaleSpec{ + Replicas: int32(replicas), + }, + } + + if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc); err != nil { + t.Fatalf("error updating scale %v: %v", update, err) + } + + obj, err = storage.Scale.Get(ctx, name, &metav1.GetOptions{}) + if err != nil { + t.Fatalf("error fetching scale for %s: %v", name, err) + } + scale = obj.(*autoscalingv1.Scale) + if scale.Spec.Replicas != int32(replicas) { + t.Errorf("wrong replicas count: expected: %d got: %d", replicas, scale.Spec.Replicas) + } + + update.ResourceVersion = scale.ResourceVersion + update.Spec.Replicas = 15 + + if _, _, err = storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc); err != nil && !errors.IsConflict(err) { + t.Fatalf("unexpected error, expecting an update conflict but got %v", err) + } +} + +func TestScaleUpdateWithoutSpecReplicas(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + defer storage.CustomResource.Store.DestroyFunc() + + name := "foo" + + var cr unstructured.Unstructured + ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault) + key := "/noxus/" + metav1.NamespaceDefault + "/" + name + withoutSpecReplicas := validCustomResource.DeepCopy() + unstructured.RemoveNestedField(withoutSpecReplicas.Object, "spec", "replicas") + if err := storage.CustomResource.Storage.Create(ctx, key, withoutSpecReplicas, &cr, 0); err != nil { + t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, withoutSpecReplicas, err) + } + + replicas := 12 + update := autoscalingv1.Scale{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + ResourceVersion: cr.GetResourceVersion(), + }, + Spec: autoscalingv1.ScaleSpec{ + Replicas: int32(replicas), + }, + } + + if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc); err != nil { + t.Fatalf("error updating scale %v: %v", update, err) + } + + obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{}) + if err != nil { + t.Fatalf("error fetching scale for %s: %v", name, err) + } + scale := obj.(*autoscalingv1.Scale) + if scale.Spec.Replicas != int32(replicas) { + t.Errorf("wrong replicas count: expected: %d got: %d", replicas, scale.Spec.Replicas) + } +} + +type unstructuredJsonCodec struct{} + +func (c unstructuredJsonCodec) Decode(data []byte, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + obj := into.(*unstructured.Unstructured) + err := obj.UnmarshalJSON(data) + if err != nil { + return nil, nil, err + } + gvk := obj.GroupVersionKind() + return obj, &gvk, nil +} + +func (c unstructuredJsonCodec) Encode(obj runtime.Object, w io.Writer) error { + u := obj.(*unstructured.Unstructured) + bs, err := u.MarshalJSON() + if err != nil { + return err + } + w.Write(bs) + return nil +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/registry.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/registry.go new file mode 100644 index 00000000000..a0fd086d0ac --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/registry.go @@ -0,0 +1,104 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package customresource + +import ( + "fmt" + "strings" + + "k8s.io/apimachinery/pkg/api/errors" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/watch" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" +) + +// Registry is an interface for things that know how to store CustomResources. +type Registry interface { + ListCustomResources(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*unstructured.UnstructuredList, error) + WatchCustomResources(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) + GetCustomResource(ctx genericapirequest.Context, customResourceID string, options *metav1.GetOptions) (*unstructured.Unstructured, error) + CreateCustomResource(ctx genericapirequest.Context, customResource *unstructured.Unstructured, createValidation rest.ValidateObjectFunc) (*unstructured.Unstructured, error) + UpdateCustomResource(ctx genericapirequest.Context, customResource *unstructured.Unstructured, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (*unstructured.Unstructured, error) + DeleteCustomResource(ctx genericapirequest.Context, customResourceID string) error +} + +// storage puts strong typing around storage calls +type storage struct { + rest.StandardStorage +} + +// NewRegistry returns a new Registry interface for the given Storage. Any mismatched +// types will panic. +func NewRegistry(s rest.StandardStorage) Registry { + return &storage{s} +} + +func (s *storage) ListCustomResources(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*unstructured.UnstructuredList, error) { + if options != nil && options.FieldSelector != nil && !options.FieldSelector.Empty() { + return nil, fmt.Errorf("field selector not supported yet") + } + obj, err := s.List(ctx, options) + if err != nil { + return nil, err + } + return obj.(*unstructured.UnstructuredList), err +} + +func (s *storage) WatchCustomResources(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) { + return s.Watch(ctx, options) +} + +func (s *storage) GetCustomResource(ctx genericapirequest.Context, customResourceID string, options *metav1.GetOptions) (*unstructured.Unstructured, error) { + obj, err := s.Get(ctx, customResourceID, options) + customResource, ok := obj.(*unstructured.Unstructured) + if !ok { + return nil, fmt.Errorf("custom resource must be of type Unstructured") + } + + if err != nil { + apiVersion := customResource.GetAPIVersion() + groupVersion := strings.Split(apiVersion, "/") + group := groupVersion[0] + return nil, errors.NewNotFound(schema.GroupResource{Group: group, Resource: "scale"}, customResourceID) + } + return customResource, nil +} + +func (s *storage) CreateCustomResource(ctx genericapirequest.Context, customResource *unstructured.Unstructured, createValidation rest.ValidateObjectFunc) (*unstructured.Unstructured, error) { + obj, err := s.Create(ctx, customResource, rest.ValidateAllObjectFunc, false) + if err != nil { + return nil, err + } + return obj.(*unstructured.Unstructured), nil +} + +func (s *storage) UpdateCustomResource(ctx genericapirequest.Context, customResource *unstructured.Unstructured, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc) (*unstructured.Unstructured, error) { + obj, _, err := s.Update(ctx, customResource.GetName(), rest.DefaultUpdatedObjectInfo(customResource), createValidation, updateValidation) + if err != nil { + return nil, err + } + return obj.(*unstructured.Unstructured), nil +} + +func (s *storage) DeleteCustomResource(ctx genericapirequest.Context, customResourceID string) error { + _, _, err := s.Delete(ctx, customResourceID, nil) + return err +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go new file mode 100644 index 00000000000..dab6bf47a3f --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go @@ -0,0 +1,62 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package customresource + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" +) + +type statusStrategy struct { + customResourceStrategy +} + +func NewStatusStrategy(strategy customResourceStrategy) statusStrategy { + return statusStrategy{strategy} +} + +func (a statusStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) { + newCustomResourceObject := obj.(*unstructured.Unstructured) + oldCustomResourceObject := old.(*unstructured.Unstructured) + + newCustomResource := newCustomResourceObject.UnstructuredContent() + oldCustomResource := oldCustomResourceObject.UnstructuredContent() + + // update is not allowed to set spec and metadata + _, ok1 := newCustomResource["spec"] + _, ok2 := oldCustomResource["spec"] + switch { + case ok2: + newCustomResource["spec"] = oldCustomResource["spec"] + case ok1: + delete(newCustomResource, "spec") + } + + newCustomResourceObject.SetAnnotations(oldCustomResourceObject.GetAnnotations()) + newCustomResourceObject.SetFinalizers(oldCustomResourceObject.GetFinalizers()) + newCustomResourceObject.SetGeneration(oldCustomResourceObject.GetGeneration()) + newCustomResourceObject.SetLabels(oldCustomResourceObject.GetLabels()) + newCustomResourceObject.SetOwnerReferences(oldCustomResourceObject.GetOwnerReferences()) + newCustomResourceObject.SetSelfLink(oldCustomResourceObject.GetSelfLink()) +} + +// ValidateUpdate is the default update validation for an end user updating status. +func (a statusStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList { + return a.customResourceStrategy.validator.ValidateStatusUpdate(ctx, obj, old, a.scale) +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go index f001dc57a0f..092bfc89c4e 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go @@ -17,12 +17,10 @@ limitations under the License. package customresource import ( - "fmt" - "github.com/go-openapi/validate" + apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/api/validation" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/fields" @@ -31,63 +29,129 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/apiserver/pkg/storage" + apiserverstorage "k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage/names" + utilfeature "k8s.io/apiserver/pkg/util/feature" - apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features" ) -type customResourceDefinitionStorageStrategy struct { +// customResourceStrategy implements behavior for CustomResources. +type customResourceStrategy struct { runtime.ObjectTyper names.NameGenerator namespaceScoped bool validator customResourceValidator + status *apiextensions.CustomResourceSubresourceStatus + scale *apiextensions.CustomResourceSubresourceScale } -func NewStrategy(typer runtime.ObjectTyper, namespaceScoped bool, kind schema.GroupVersionKind, validator *validate.SchemaValidator) customResourceDefinitionStorageStrategy { - return customResourceDefinitionStorageStrategy{ +func NewStrategy(typer runtime.ObjectTyper, namespaceScoped bool, kind schema.GroupVersionKind, schemaValidator, statusSchemaValidator *validate.SchemaValidator, status *apiextensions.CustomResourceSubresourceStatus, scale *apiextensions.CustomResourceSubresourceScale) customResourceStrategy { + return customResourceStrategy{ ObjectTyper: typer, NameGenerator: names.SimpleNameGenerator, namespaceScoped: namespaceScoped, + status: status, + scale: scale, validator: customResourceValidator{ - namespaceScoped: namespaceScoped, - kind: kind, - validator: validator, + namespaceScoped: namespaceScoped, + kind: kind, + schemaValidator: schemaValidator, + statusSchemaValidator: statusSchemaValidator, }, } } -func (a customResourceDefinitionStorageStrategy) NamespaceScoped() bool { +func (a customResourceStrategy) NamespaceScoped() bool { return a.namespaceScoped } -func (customResourceDefinitionStorageStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) { +// PrepareForCreate clears the status of a CustomResource before creation. +func (a customResourceStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) { + if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && a.status != nil { + customResourceObject := obj.(*unstructured.Unstructured) + customResource := customResourceObject.UnstructuredContent() + + // create cannot set status + if _, ok := customResource["status"]; ok { + delete(customResource, "status") + } + } + + accessor, _ := meta.Accessor(obj) + accessor.SetGeneration(1) } -func (customResourceDefinitionStorageStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) { +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (a customResourceStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) { + if !utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) || a.status == nil { + return + } + + newCustomResourceObject := obj.(*unstructured.Unstructured) + oldCustomResourceObject := old.(*unstructured.Unstructured) + + newCustomResource := newCustomResourceObject.UnstructuredContent() + oldCustomResource := oldCustomResourceObject.UnstructuredContent() + + // update is not allowed to set status + _, ok1 := newCustomResource["status"] + _, ok2 := oldCustomResource["status"] + switch { + case ok2: + newCustomResource["status"] = oldCustomResource["status"] + case ok1: + delete(newCustomResource, "status") + } + + // Any changes to the spec increment the generation number, any changes to the + // status should reflect the generation number of the corresponding object. We push + // the burden of managing the status onto the clients because we can't (in general) + // know here what version of spec the writer of the status has seen. It may seem like + // we can at first -- since obj contains spec -- but in the future we will probably make + // status its own object, and even if we don't, writes may be the result of a + // read-update-write loop, so the contents of spec may not actually be the spec that + // the CustomResource has *seen*. + newSpec, ok1 := newCustomResource["spec"] + oldSpec, ok2 := oldCustomResource["spec"] + + // spec is changed, created or deleted + if (ok1 && ok2 && !apiequality.Semantic.DeepEqual(oldSpec, newSpec)) || (ok1 && !ok2) || (!ok1 && ok2) { + oldAccessor, _ := meta.Accessor(oldCustomResourceObject) + newAccessor, _ := meta.Accessor(newCustomResourceObject) + newAccessor.SetGeneration(oldAccessor.GetGeneration() + 1) + } } -func (a customResourceDefinitionStorageStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList { - return a.validator.Validate(ctx, obj) +// Validate validates a new CustomResource. +func (a customResourceStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList { + return a.validator.Validate(ctx, obj, a.scale) } -func (customResourceDefinitionStorageStrategy) AllowCreateOnUpdate() bool { +// Canonicalize normalizes the object after validation. +func (customResourceStrategy) Canonicalize(obj runtime.Object) { +} + +// AllowCreateOnUpdate is false for CustomResources; this means a POST is +// needed to create one. +func (customResourceStrategy) AllowCreateOnUpdate() bool { return false } -func (customResourceDefinitionStorageStrategy) AllowUnconditionalUpdate() bool { +// AllowUnconditionalUpdate is the default update policy for CustomResource objects. +func (customResourceStrategy) AllowUnconditionalUpdate() bool { return false } -func (customResourceDefinitionStorageStrategy) Canonicalize(obj runtime.Object) { +// ValidateUpdate is the default update validation for an end user updating status. +func (a customResourceStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList { + return a.validator.ValidateUpdate(ctx, obj, old, a.scale) } -func (a customResourceDefinitionStorageStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList { - return a.validator.ValidateUpdate(ctx, obj, old) -} - -func (a customResourceDefinitionStorageStrategy) GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { +// GetAttrs returns labels and fields of a given object for filtering purposes. +func (a customResourceStrategy) GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { accessor, err := meta.Accessor(obj) if err != nil { return nil, nil, false, err @@ -108,80 +172,13 @@ func objectMetaFieldsSet(objectMeta metav1.Object, namespaceScoped bool) fields. } } -func (a customResourceDefinitionStorageStrategy) MatchCustomResourceDefinitionStorage(label labels.Selector, field fields.Selector) storage.SelectionPredicate { - return storage.SelectionPredicate{ +// MatchCustomResourceDefinitionStorage is the filter used by the generic etcd backend to route +// watch events from etcd to clients of the apiserver only interested in specific +// labels/fields. +func (a customResourceStrategy) MatchCustomResourceDefinitionStorage(label labels.Selector, field fields.Selector) apiserverstorage.SelectionPredicate { + return apiserverstorage.SelectionPredicate{ Label: label, Field: field, GetAttrs: a.GetAttrs, } } - -type customResourceValidator struct { - namespaceScoped bool - kind schema.GroupVersionKind - validator *validate.SchemaValidator -} - -func (a customResourceValidator) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList { - accessor, err := meta.Accessor(obj) - if err != nil { - return field.ErrorList{field.Invalid(field.NewPath("metadata"), nil, err.Error())} - } - typeAccessor, err := meta.TypeAccessor(obj) - if err != nil { - return field.ErrorList{field.Invalid(field.NewPath("kind"), nil, err.Error())} - } - if typeAccessor.GetKind() != a.kind.Kind { - return field.ErrorList{field.Invalid(field.NewPath("kind"), typeAccessor.GetKind(), fmt.Sprintf("must be %v", a.kind.Kind))} - } - if typeAccessor.GetAPIVersion() != a.kind.Group+"/"+a.kind.Version { - return field.ErrorList{field.Invalid(field.NewPath("apiVersion"), typeAccessor.GetAPIVersion(), fmt.Sprintf("must be %v", a.kind.Group+"/"+a.kind.Version))} - } - - customResourceObject, ok := obj.(*unstructured.Unstructured) - // this will never happen. - if !ok { - return field.ErrorList{field.Invalid(field.NewPath(""), customResourceObject, fmt.Sprintf("has type %T. Must be a pointer to an Unstructured type", customResourceObject))} - } - - customResource := customResourceObject.UnstructuredContent() - if err = apiservervalidation.ValidateCustomResource(customResource, a.validator); err != nil { - return field.ErrorList{field.Invalid(field.NewPath(""), customResource, err.Error())} - } - - return validation.ValidateObjectMetaAccessor(accessor, a.namespaceScoped, validation.NameIsDNSSubdomain, field.NewPath("metadata")) -} - -func (a customResourceValidator) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList { - objAccessor, err := meta.Accessor(obj) - if err != nil { - return field.ErrorList{field.Invalid(field.NewPath("metadata"), nil, err.Error())} - } - oldAccessor, err := meta.Accessor(old) - if err != nil { - return field.ErrorList{field.Invalid(field.NewPath("metadata"), nil, err.Error())} - } - typeAccessor, err := meta.TypeAccessor(obj) - if err != nil { - return field.ErrorList{field.Invalid(field.NewPath("kind"), nil, err.Error())} - } - if typeAccessor.GetKind() != a.kind.Kind { - return field.ErrorList{field.Invalid(field.NewPath("kind"), typeAccessor.GetKind(), fmt.Sprintf("must be %v", a.kind.Kind))} - } - if typeAccessor.GetAPIVersion() != a.kind.Group+"/"+a.kind.Version { - return field.ErrorList{field.Invalid(field.NewPath("apiVersion"), typeAccessor.GetAPIVersion(), fmt.Sprintf("must be %v", a.kind.Group+"/"+a.kind.Version))} - } - - customResourceObject, ok := obj.(*unstructured.Unstructured) - // this will never happen. - if !ok { - return field.ErrorList{field.Invalid(field.NewPath(""), customResourceObject, fmt.Sprintf("has type %T. Must be a pointer to an Unstructured type", customResourceObject))} - } - - customResource := customResourceObject.UnstructuredContent() - if err = apiservervalidation.ValidateCustomResource(customResource, a.validator); err != nil { - return field.ErrorList{field.Invalid(field.NewPath(""), customResource, err.Error())} - } - - return validation.ValidateObjectMetaAccessorUpdate(objAccessor, oldAccessor, field.NewPath("metadata")) -} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/validator.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/validator.go new file mode 100644 index 00000000000..ef5023dc63a --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/validator.go @@ -0,0 +1,241 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package customresource + +import ( + "fmt" + "math" + "strings" + + "github.com/go-openapi/validate" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/api/validation" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation" +) + +type customResourceValidator struct { + namespaceScoped bool + kind schema.GroupVersionKind + schemaValidator *validate.SchemaValidator + statusSchemaValidator *validate.SchemaValidator +} + +func (a customResourceValidator) Validate(ctx genericapirequest.Context, obj runtime.Object, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList { + accessor, err := meta.Accessor(obj) + if err != nil { + return field.ErrorList{field.Invalid(field.NewPath("metadata"), nil, err.Error())} + } + typeAccessor, err := meta.TypeAccessor(obj) + if err != nil { + return field.ErrorList{field.Invalid(field.NewPath("kind"), nil, err.Error())} + } + if typeAccessor.GetKind() != a.kind.Kind { + return field.ErrorList{field.Invalid(field.NewPath("kind"), typeAccessor.GetKind(), fmt.Sprintf("must be %v", a.kind.Kind))} + } + if typeAccessor.GetAPIVersion() != a.kind.Group+"/"+a.kind.Version { + return field.ErrorList{field.Invalid(field.NewPath("apiVersion"), typeAccessor.GetAPIVersion(), fmt.Sprintf("must be %v", a.kind.Group+"/"+a.kind.Version))} + } + + customResourceObject, ok := obj.(*unstructured.Unstructured) + // this will never happen. + if !ok { + return field.ErrorList{field.Invalid(field.NewPath(""), customResourceObject, fmt.Sprintf("has type %T. Must be a pointer to an Unstructured type", customResourceObject))} + } + customResource := customResourceObject.UnstructuredContent() + + if err = apiservervalidation.ValidateCustomResource(customResource, a.schemaValidator); err != nil { + return field.ErrorList{field.Invalid(field.NewPath(""), customResource, err.Error())} + } + + if scale != nil { + // validate specReplicas + specReplicasPath := strings.TrimPrefix(scale.SpecReplicasPath, ".") // ignore leading period + specReplicas, _, err := unstructured.NestedInt64(customResource, strings.Split(specReplicasPath, ".")...) + if err != nil { + return field.ErrorList{field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, err.Error())} + } + if specReplicas < 0 { + return field.ErrorList{field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, "should be a non-negative integer")} + } + if specReplicas > math.MaxInt32 { + return field.ErrorList{field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, fmt.Sprintf("should be less than or equal to %v", math.MaxInt32))} + } + + // validate statusReplicas + statusReplicasPath := strings.TrimPrefix(scale.StatusReplicasPath, ".") // ignore leading period + statusReplicas, _, err := unstructured.NestedInt64(customResource, strings.Split(statusReplicasPath, ".")...) + if err != nil { + return field.ErrorList{field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, err.Error())} + } + if statusReplicas < 0 { + return field.ErrorList{field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, "should be a non-negative integer")} + } + if statusReplicas > math.MaxInt32 { + return field.ErrorList{field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, fmt.Sprintf("should be less than or equal to %v", math.MaxInt32))} + } + + // validate labelSelector + if scale.LabelSelectorPath != nil { + labelSelectorPath := strings.TrimPrefix(*scale.LabelSelectorPath, ".") // ignore leading period + labelSelector, _, err := unstructured.NestedString(customResource, strings.Split(labelSelectorPath, ".")...) + if err != nil { + return field.ErrorList{field.Invalid(field.NewPath(*scale.LabelSelectorPath), labelSelector, err.Error())} + } + } + } + + return validation.ValidateObjectMetaAccessor(accessor, a.namespaceScoped, validation.NameIsDNSSubdomain, field.NewPath("metadata")) +} + +func (a customResourceValidator) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList { + objAccessor, err := meta.Accessor(obj) + if err != nil { + return field.ErrorList{field.Invalid(field.NewPath("metadata"), nil, err.Error())} + } + oldAccessor, err := meta.Accessor(old) + if err != nil { + return field.ErrorList{field.Invalid(field.NewPath("metadata"), nil, err.Error())} + } + typeAccessor, err := meta.TypeAccessor(obj) + if err != nil { + return field.ErrorList{field.Invalid(field.NewPath("kind"), nil, err.Error())} + } + if typeAccessor.GetKind() != a.kind.Kind { + return field.ErrorList{field.Invalid(field.NewPath("kind"), typeAccessor.GetKind(), fmt.Sprintf("must be %v", a.kind.Kind))} + } + if typeAccessor.GetAPIVersion() != a.kind.Group+"/"+a.kind.Version { + return field.ErrorList{field.Invalid(field.NewPath("apiVersion"), typeAccessor.GetAPIVersion(), fmt.Sprintf("must be %v", a.kind.Group+"/"+a.kind.Version))} + } + + customResourceObject, ok := obj.(*unstructured.Unstructured) + // this will never happen. + if !ok { + return field.ErrorList{field.Invalid(field.NewPath(""), customResourceObject, fmt.Sprintf("has type %T. Must be a pointer to an Unstructured type", customResourceObject))} + } + customResource := customResourceObject.UnstructuredContent() + + if err = apiservervalidation.ValidateCustomResource(customResource, a.schemaValidator); err != nil { + return field.ErrorList{field.Invalid(field.NewPath(""), customResource, err.Error())} + } + + if scale != nil { + // validate specReplicas + specReplicasPath := strings.TrimPrefix(scale.SpecReplicasPath, ".") // ignore leading period + specReplicas, _, err := unstructured.NestedInt64(customResource, strings.Split(specReplicasPath, ".")...) + if err != nil { + return field.ErrorList{field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, err.Error())} + } + if specReplicas < 0 { + return field.ErrorList{field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, "should be a non-negative integer")} + } + if specReplicas > math.MaxInt32 { + return field.ErrorList{field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, fmt.Sprintf("should be less than or equal to %v", math.MaxInt32))} + } + + // validate statusReplicas + statusReplicasPath := strings.TrimPrefix(scale.StatusReplicasPath, ".") // ignore leading period + statusReplicas, _, err := unstructured.NestedInt64(customResource, strings.Split(statusReplicasPath, ".")...) + if err != nil { + return field.ErrorList{field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, err.Error())} + } + if statusReplicas < 0 { + return field.ErrorList{field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, "should be a non-negative integer")} + } + if statusReplicas > math.MaxInt32 { + return field.ErrorList{field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, fmt.Sprintf("should be less than or equal to %v", math.MaxInt32))} + } + + // validate labelSelector + if scale.LabelSelectorPath != nil { + labelSelectorPath := strings.TrimPrefix(*scale.LabelSelectorPath, ".") // ignore leading period + labelSelector, _, err := unstructured.NestedString(customResource, strings.Split(labelSelectorPath, ".")...) + if err != nil { + return field.ErrorList{field.Invalid(field.NewPath(*scale.LabelSelectorPath), labelSelector, err.Error())} + } + } + } + + return validation.ValidateObjectMetaAccessorUpdate(objAccessor, oldAccessor, field.NewPath("metadata")) +} + +func (a customResourceValidator) ValidateStatusUpdate(ctx genericapirequest.Context, obj, old runtime.Object, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList { + objAccessor, err := meta.Accessor(obj) + if err != nil { + return field.ErrorList{field.Invalid(field.NewPath("metadata"), nil, err.Error())} + } + oldAccessor, err := meta.Accessor(old) + if err != nil { + return field.ErrorList{field.Invalid(field.NewPath("metadata"), nil, err.Error())} + } + typeAccessor, err := meta.TypeAccessor(obj) + if err != nil { + return field.ErrorList{field.Invalid(field.NewPath("kind"), nil, err.Error())} + } + if typeAccessor.GetKind() != a.kind.Kind { + return field.ErrorList{field.Invalid(field.NewPath("kind"), typeAccessor.GetKind(), fmt.Sprintf("must be %v", a.kind.Kind))} + } + if typeAccessor.GetAPIVersion() != a.kind.Group+"/"+a.kind.Version { + return field.ErrorList{field.Invalid(field.NewPath("apiVersion"), typeAccessor.GetAPIVersion(), fmt.Sprintf("must be %v", a.kind.Group+"/"+a.kind.Version))} + } + + customResourceObject, ok := obj.(*unstructured.Unstructured) + // this will never happen. + if !ok { + return field.ErrorList{field.Invalid(field.NewPath(""), customResourceObject, fmt.Sprintf("has type %T. Must be a pointer to an Unstructured type", customResourceObject))} + } + customResource := customResourceObject.UnstructuredContent() + + // validate only the status + customResourceStatus := customResource["status"] + if err = apiservervalidation.ValidateCustomResource(customResourceStatus, a.statusSchemaValidator); err != nil { + return field.ErrorList{field.Invalid(field.NewPath("status"), customResourceStatus, err.Error())} + } + + if scale != nil { + // validate statusReplicas + statusReplicasPath := strings.TrimPrefix(scale.StatusReplicasPath, ".") // ignore leading period + statusReplicas, _, err := unstructured.NestedInt64(customResource, strings.Split(statusReplicasPath, ".")...) + if err != nil { + return field.ErrorList{field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, err.Error())} + } + if statusReplicas < 0 { + return field.ErrorList{field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, "should be a non-negative integer")} + } + if statusReplicas > math.MaxInt32 { + return field.ErrorList{field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, fmt.Sprintf("should be less than or equal to %v", math.MaxInt32))} + } + + // validate labelSelector + if scale.LabelSelectorPath != nil { + labelSelectorPath := strings.TrimPrefix(*scale.LabelSelectorPath, ".") // ignore leading period + labelSelector, _, err := unstructured.NestedString(customResource, strings.Split(labelSelectorPath, ".")...) + if err != nil { + return field.ErrorList{field.Invalid(field.NewPath(*scale.LabelSelectorPath), labelSelector, err.Error())} + } + } + } + + return validation.ValidateObjectMetaAccessorUpdate(objAccessor, oldAccessor, field.NewPath("metadata")) +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go index 849cd6f8ea7..7e44fa4c2bb 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go @@ -35,6 +35,7 @@ import ( apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features" ) +// strategy implements behavior for CustomResources. type strategy struct { runtime.ObjectTyper names.NameGenerator @@ -48,6 +49,7 @@ func (strategy) NamespaceScoped() bool { return false } +// PrepareForCreate clears the status of a CustomResourceDefinition before creation. func (strategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) { crd := obj.(*apiextensions.CustomResourceDefinition) crd.Status = apiextensions.CustomResourceDefinitionStatus{} @@ -57,8 +59,12 @@ func (strategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Obje if !utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceValidation) { crd.Spec.Validation = nil } + if !utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) { + crd.Spec.Subresources = nil + } } +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. func (strategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) { newCRD := obj.(*apiextensions.CustomResourceDefinition) oldCRD := old.(*apiextensions.CustomResourceDefinition) @@ -80,23 +86,33 @@ func (strategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime newCRD.Spec.Validation = nil oldCRD.Spec.Validation = nil } + if !utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) { + newCRD.Spec.Subresources = nil + oldCRD.Spec.Subresources = nil + } } +// Validate validates a new CustomResourceDefinition. func (strategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList { return validation.ValidateCustomResourceDefinition(obj.(*apiextensions.CustomResourceDefinition)) } +// AllowCreateOnUpdate is false for CustomResourceDefinition; this means a POST is +// needed to create one. func (strategy) AllowCreateOnUpdate() bool { return false } +// AllowUnconditionalUpdate is the default update policy for CustomResourceDefinition objects. func (strategy) AllowUnconditionalUpdate() bool { return false } +// Canonicalize normalizes the object after validation. func (strategy) Canonicalize(obj runtime.Object) { } +// ValidateUpdate is the default update validation for an end user updating status. func (strategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList { return validation.ValidateCustomResourceDefinitionUpdate(obj.(*apiextensions.CustomResourceDefinition), old.(*apiextensions.CustomResourceDefinition)) } @@ -143,10 +159,11 @@ func (statusStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old run return validation.ValidateUpdateCustomResourceDefinitionStatus(obj.(*apiextensions.CustomResourceDefinition), old.(*apiextensions.CustomResourceDefinition)) } +// GetAttrs returns labels and fields of a given object for filtering purposes. func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) { apiserver, ok := obj.(*apiextensions.CustomResourceDefinition) if !ok { - return nil, nil, false, fmt.Errorf("given object is not a CustomResourceDefinition.") + return nil, nil, false, fmt.Errorf("given object is not a CustomResourceDefinition") } return labels.Set(apiserver.ObjectMeta.Labels), CustomResourceDefinitionToSelectableFields(apiserver), apiserver.Initializers != nil, nil } diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/basic_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/basic_test.go index afb6d1b495e..2df2076bf0f 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/basic_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/basic_test.go @@ -35,7 +35,7 @@ import ( ) func TestServerUp(t *testing.T) { - stopCh, _, _, err := testserver.StartDefaultServer() + stopCh, _, _, err := testserver.StartDefaultServerWithClients() if err != nil { t.Fatal(err) } @@ -43,7 +43,7 @@ func TestServerUp(t *testing.T) { } func TestNamespaceScopedCRUD(t *testing.T) { - stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() if err != nil { t.Fatal(err) } @@ -61,7 +61,7 @@ func TestNamespaceScopedCRUD(t *testing.T) { } func TestClusterScopedCRUD(t *testing.T) { - stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() if err != nil { t.Fatal(err) } @@ -347,7 +347,7 @@ func TestDiscovery(t *testing.T) { group := "mygroup.example.com" version := "v1beta1" - stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() if err != nil { t.Fatal(err) } @@ -391,7 +391,7 @@ func TestDiscovery(t *testing.T) { } func TestNoNamespaceReject(t *testing.T) { - stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() if err != nil { t.Fatal(err) } @@ -430,7 +430,7 @@ func TestNoNamespaceReject(t *testing.T) { } func TestSameNameDiffNamespace(t *testing.T) { - stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() if err != nil { t.Fatal(err) } @@ -450,7 +450,7 @@ func TestSameNameDiffNamespace(t *testing.T) { } func TestSelfLink(t *testing.T) { - stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() if err != nil { t.Fatal(err) } @@ -503,7 +503,7 @@ func TestSelfLink(t *testing.T) { } func TestPreserveInt(t *testing.T) { - stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() if err != nil { t.Fatal(err) } @@ -548,7 +548,7 @@ func TestPreserveInt(t *testing.T) { } func TestPatch(t *testing.T) { - stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() if err != nil { t.Fatal(err) } @@ -622,7 +622,7 @@ func TestPatch(t *testing.T) { } func TestCrossNamespaceListWatch(t *testing.T) { - stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() if err != nil { t.Fatal(err) } @@ -758,7 +758,7 @@ func checkNamespacesWatchHelper(t *testing.T, ns string, namespacedwatch watch.I } func TestNameConflict(t *testing.T) { - stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() if err != nil { t.Fatal(err) } diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/finalization_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/finalization_test.go index 12634870431..57b04884b93 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/finalization_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/finalization_test.go @@ -28,7 +28,7 @@ import ( ) func TestFinalization(t *testing.T) { - stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() require.NoError(t, err) defer close(stopCh) diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/registration_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/registration_test.go index a72ce2dcf9e..7cdd2bc7c77 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/registration_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/registration_test.go @@ -30,6 +30,7 @@ import ( apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" extensionsapiserver "k8s.io/apiextensions-apiserver/pkg/apiserver" + apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/apiextensions-apiserver/test/integration/testserver" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" @@ -73,8 +74,22 @@ func NewNamespacedCustomResourceClient(ns string, client dynamic.Interface, defi }, ns) } +func NewNamespacedCustomResourceStatusClient(ns string, client dynamic.Interface, definition *apiextensionsv1beta1.CustomResourceDefinition) dynamic.ResourceInterface { + return client.Resource(&metav1.APIResource{ + Name: definition.Spec.Names.Plural + "/status", + Namespaced: definition.Spec.Scope == apiextensionsv1beta1.NamespaceScoped, + }, ns) +} + +func NewNamespacedCustomResourceScaleClient(ns string, client dynamic.Interface, definition *apiextensionsv1beta1.CustomResourceDefinition) dynamic.ResourceInterface { + return client.Resource(&metav1.APIResource{ + Name: definition.Spec.Names.Plural + "/scale", + Namespaced: definition.Spec.Scope == apiextensionsv1beta1.NamespaceScoped, + }, ns) +} + func TestMultipleResourceInstances(t *testing.T) { - stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() if err != nil { t.Fatal(err) } @@ -198,7 +213,7 @@ func TestMultipleResourceInstances(t *testing.T) { } func TestMultipleRegistration(t *testing.T) { - stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() if err != nil { t.Fatal(err) } @@ -254,7 +269,7 @@ func TestMultipleRegistration(t *testing.T) { } func TestDeRegistrationAndReRegistration(t *testing.T) { - stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() if err != nil { t.Fatal(err) } @@ -347,12 +362,18 @@ func TestEtcdStorage(t *testing.T) { if err != nil { t.Fatal(err) } - stopCh, apiExtensionClient, clientPool, err := testserver.StartServer(config) + stopCh, clientConfig, err := testserver.StartServer(config) if err != nil { t.Fatal(err) } defer close(stopCh) + apiExtensionClient, err := apiextensionsclientset.NewForConfig(clientConfig) + if err != nil { + t.Fatal(err) + } + clientPool := dynamic.NewDynamicClientPool(clientConfig) + etcdPrefix := getPrefixFromConfig(t, config) ns1 := "another-default-is-possible" diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/subresources_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/subresources_test.go new file mode 100644 index 00000000000..7f04c8215eb --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/subresources_test.go @@ -0,0 +1,787 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package integration + +import ( + "math" + "reflect" + "sort" + "strings" + "testing" + "time" + + autoscaling "k8s.io/api/autoscaling/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + utilfeature "k8s.io/apiserver/pkg/util/feature" + utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing" + "k8s.io/client-go/dynamic" + + apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "k8s.io/apiextensions-apiserver/pkg/features" + "k8s.io/apiextensions-apiserver/test/integration/testserver" +) + +var labelSelectorPath = ".status.labelSelector" + +func NewNoxuSubresourcesCRD(scope apiextensionsv1beta1.ResourceScope) *apiextensionsv1beta1.CustomResourceDefinition { + return &apiextensionsv1beta1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: "noxus.mygroup.example.com"}, + Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{ + Group: "mygroup.example.com", + Version: "v1beta1", + Names: apiextensionsv1beta1.CustomResourceDefinitionNames{ + Plural: "noxus", + Singular: "nonenglishnoxu", + Kind: "WishIHadChosenNoxu", + ShortNames: []string{"foo", "bar", "abc", "def"}, + ListKind: "NoxuItemList", + }, + Scope: scope, + Subresources: &apiextensionsv1beta1.CustomResourceSubresources{ + Status: &apiextensionsv1beta1.CustomResourceSubresourceStatus{}, + Scale: &apiextensionsv1beta1.CustomResourceSubresourceScale{ + SpecReplicasPath: ".spec.replicas", + StatusReplicasPath: ".status.replicas", + LabelSelectorPath: &labelSelectorPath, + }, + }, + }, + } +} + +func NewNoxuSubresourceInstance(namespace, name string) *unstructured.Unstructured { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "mygroup.example.com/v1beta1", + "kind": "WishIHadChosenNoxu", + "metadata": map[string]interface{}{ + "namespace": namespace, + "name": name, + }, + "spec": map[string]interface{}{ + "num": int64(10), + "replicas": int64(3), + }, + "status": map[string]interface{}{ + "replicas": int64(7), + }, + }, + } +} + +func TestStatusSubresource(t *testing.T) { + // enable alpha feature CustomResourceSubresources + defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CustomResourceSubresources, true)() + + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() + if err != nil { + t.Fatal(err) + } + defer close(stopCh) + + noxuDefinition := NewNoxuSubresourcesCRD(apiextensionsv1beta1.NamespaceScoped) + noxuVersionClient, err := testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool) + if err != nil { + t.Fatal(err) + } + + ns := "not-the-default" + noxuResourceClient := NewNamespacedCustomResourceClient(ns, noxuVersionClient, noxuDefinition) + noxuStatusResourceClient := NewNamespacedCustomResourceStatusClient(ns, noxuVersionClient, noxuDefinition) + _, err = instantiateCustomResource(t, NewNoxuSubresourceInstance(ns, "foo"), noxuResourceClient, noxuDefinition) + if err != nil { + t.Fatalf("unable to create noxu instance: %v", err) + } + + gottenNoxuInstance, err := noxuResourceClient.Get("foo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err) + } + + // status should not be set after creation + if val, ok := gottenNoxuInstance.Object["status"]; ok { + t.Fatalf("status should not be set after creation, got %v", val) + } + + // .status.num = 20 + err = unstructured.SetNestedField(gottenNoxuInstance.Object, int64(20), "status", "num") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // .spec.num = 20 + err = unstructured.SetNestedField(gottenNoxuInstance.Object, int64(20), "spec", "num") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // UpdateStatus should not update spec. + // Check that .spec.num = 10 and .status.num = 20 + updatedStatusInstance, err := noxuStatusResourceClient.Update(gottenNoxuInstance) + if err != nil { + t.Fatalf("unable to update status: %v", err) + } + + specNum, found, err := unstructured.NestedInt64(updatedStatusInstance.Object, "spec", "num") + if !found || err != nil { + t.Fatalf("unable to get .spec.num") + } + if specNum != int64(10) { + t.Fatalf(".spec.num: expected: %v, got: %v", int64(10), specNum) + } + + statusNum, found, err := unstructured.NestedInt64(updatedStatusInstance.Object, "status", "num") + if !found || err != nil { + t.Fatalf("unable to get .status.num") + } + if statusNum != int64(20) { + t.Fatalf(".status.num: expected: %v, got: %v", int64(20), statusNum) + } + + gottenNoxuInstance, err = noxuResourceClient.Get("foo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err) + } + + // .status.num = 40 + err = unstructured.SetNestedField(gottenNoxuInstance.Object, int64(40), "status", "num") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // .spec.num = 40 + err = unstructured.SetNestedField(gottenNoxuInstance.Object, int64(40), "spec", "num") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Update should not update status. + // Check that .spec.num = 40 and .status.num = 20 + updatedInstance, err := noxuResourceClient.Update(gottenNoxuInstance) + if err != nil { + t.Fatalf("unable to update instance: %v", err) + } + + specNum, found, err = unstructured.NestedInt64(updatedInstance.Object, "spec", "num") + if !found || err != nil { + t.Fatalf("unable to get .spec.num") + } + if specNum != int64(40) { + t.Fatalf(".spec.num: expected: %v, got: %v", int64(40), specNum) + } + + statusNum, found, err = unstructured.NestedInt64(updatedInstance.Object, "status", "num") + if !found || err != nil { + t.Fatalf("unable to get .status.num") + } + if statusNum != int64(20) { + t.Fatalf(".status.num: expected: %v, got: %v", int64(20), statusNum) + } +} + +func TestScaleSubresource(t *testing.T) { + // enable alpha feature CustomResourceSubresources + defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CustomResourceSubresources, true)() + + groupResource := schema.GroupResource{ + Group: "mygroup.example.com", + Resource: "noxus", + } + + stopCh, config, err := testserver.StartDefaultServer() + if err != nil { + t.Fatal(err) + } + defer close(stopCh) + + apiExtensionClient, err := clientset.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + clientPool := dynamic.NewDynamicClientPool(config) + + noxuDefinition := NewNoxuSubresourcesCRD(apiextensionsv1beta1.NamespaceScoped) + + // set invalid json path for specReplicasPath + noxuDefinition.Spec.Subresources.Scale.SpecReplicasPath = "foo,bar" + _, err = testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool) + if err == nil { + t.Fatalf("unexpected non-error: specReplicasPath should be a valid json path under .spec") + } + + noxuDefinition.Spec.Subresources.Scale.SpecReplicasPath = ".spec.replicas" + noxuVersionClient, err := testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool) + if err != nil { + t.Fatal(err) + } + + ns := "not-the-default" + noxuResourceClient := NewNamespacedCustomResourceClient(ns, noxuVersionClient, noxuDefinition) + noxuStatusResourceClient := NewNamespacedCustomResourceStatusClient(ns, noxuVersionClient, noxuDefinition) + _, err = instantiateCustomResource(t, NewNoxuSubresourceInstance(ns, "foo"), noxuResourceClient, noxuDefinition) + if err != nil { + t.Fatalf("unable to create noxu instance: %v", err) + } + + scaleClient, err := testserver.CreateNewScaleClient(noxuDefinition, config) + if err != nil { + t.Fatal(err) + } + + // set .status.labelSelector = bar + gottenNoxuInstance, err := noxuResourceClient.Get("foo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err) + } + err = unstructured.SetNestedField(gottenNoxuInstance.Object, "bar", "status", "labelSelector") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + _, err = noxuStatusResourceClient.Update(gottenNoxuInstance) + if err != nil { + t.Fatalf("unable to update status: %v", err) + } + + // get the scale object + gottenScale, err := scaleClient.Scales("not-the-default").Get(groupResource, "foo") + if err != nil { + t.Fatal(err) + } + if gottenScale.Spec.Replicas != 3 { + t.Fatalf("Scale.Spec.Replicas: expected: %v, got: %v", 3, gottenScale.Spec.Replicas) + } + if gottenScale.Status.Selector != "bar" { + t.Fatalf("Scale.Status.Selector: expected: %v, got: %v", "bar", gottenScale.Status.Selector) + } + + // check self link + expectedSelfLink := "/apis/mygroup.example.com/v1beta1/namespaces/not-the-default/noxus/foo/scale" + if gottenScale.GetSelfLink() != expectedSelfLink { + t.Fatalf("Scale.Metadata.SelfLink: expected: %v, got: %v", expectedSelfLink, gottenScale.GetSelfLink()) + } + + // update the scale object + // check that spec is updated, but status is not + gottenScale.Spec.Replicas = 5 + gottenScale.Status.Selector = "baz" + updatedScale, err := scaleClient.Scales("not-the-default").Update(groupResource, gottenScale) + if err != nil { + t.Fatal(err) + } + if updatedScale.Spec.Replicas != 5 { + t.Fatalf("replicas: expected: %v, got: %v", 5, updatedScale.Spec.Replicas) + } + if updatedScale.Status.Selector != "bar" { + t.Fatalf("scale should not update status: expected %v, got: %v", "bar", updatedScale.Status.Selector) + } + + // check that .spec.replicas = 5, but status is not updated + updatedNoxuInstance, err := noxuResourceClient.Get("foo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err) + } + specReplicas, found, err := unstructured.NestedInt64(updatedNoxuInstance.Object, "spec", "replicas") + if !found || err != nil { + t.Fatalf("unable to get .spec.replicas") + } + if specReplicas != 5 { + t.Fatalf("replicas: expected: %v, got: %v", 5, specReplicas) + } + statusLabelSelector, found, err := unstructured.NestedString(updatedNoxuInstance.Object, "status", "labelSelector") + if !found || err != nil { + t.Fatalf("unable to get .status.labelSelector") + } + if statusLabelSelector != "bar" { + t.Fatalf("scale should not update status: expected %v, got: %v", "bar", statusLabelSelector) + } + + // validate maximum value + // set .spec.replicas = math.MaxInt64 + gottenNoxuInstance, err = noxuResourceClient.Get("foo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err) + } + err = unstructured.SetNestedField(gottenNoxuInstance.Object, int64(math.MaxInt64), "spec", "replicas") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + _, err = noxuResourceClient.Update(gottenNoxuInstance) + if err == nil { + t.Fatalf("unexpected non-error: .spec.replicas should be less than 2147483647") + } +} + +func TestValidationSchema(t *testing.T) { + // enable alpha feature CustomResourceSubresources + defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CustomResourceSubresources, true)() + + stopCh, config, err := testserver.StartDefaultServer() + if err != nil { + t.Fatal(err) + } + defer close(stopCh) + + apiExtensionClient, err := clientset.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + clientPool := dynamic.NewDynamicClientPool(config) + + // fields other than properties in root schema are not allowed + noxuDefinition := newNoxuValidationCRD(apiextensionsv1beta1.NamespaceScoped) + _, err = testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool) + if err == nil { + t.Fatalf("unexpected non-error: if subresources for custom resources are enabled, only properties can be used at the root of the schema") + } + + // make sure we are not restricting fields to properties even in subschemas + noxuDefinition.Spec.Validation.OpenAPIV3Schema = &apiextensionsv1beta1.JSONSchemaProps{ + Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{ + "spec": { + Description: "Validation for spec", + Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{ + "replicas": { + Type: "integer", + }, + }, + }, + }, + } + _, err = testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool) + if err != nil { + t.Fatalf("unable to created crd %v: %v", noxuDefinition.Name, err) + } +} + +func TestValidateOnlyStatus(t *testing.T) { + // enable alpha feature CustomResourceSubresources + defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CustomResourceSubresources, true)() + + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() + if err != nil { + t.Fatal(err) + } + defer close(stopCh) + + // UpdateStatus should validate only status + // 1. create a crd with max value of .spec.num = 10 and .status.num = 10 + // 2. create a cr with .spec.num = 10 and .status.num = 10 (valid) + // 3. update the crd so that max value of .spec.num = 5 and .status.num = 10 + // 4. update the status of the cr with .status.num = 5 (spec is invalid) + // validation passes becauses spec is not validated + + // max value of spec.num = 10 and status.num = 10 + schema := &apiextensionsv1beta1.JSONSchemaProps{ + Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{ + "spec": { + Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{ + "num": { + Type: "integer", + Maximum: float64Ptr(10), + }, + }, + }, + "status": { + Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{ + "num": { + Type: "integer", + Maximum: float64Ptr(10), + }, + }, + }, + }, + } + + noxuDefinition := NewNoxuSubresourcesCRD(apiextensionsv1beta1.NamespaceScoped) + noxuDefinition.Spec.Validation = &apiextensionsv1beta1.CustomResourceValidation{ + OpenAPIV3Schema: schema, + } + + noxuVersionClient, err := testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool) + if err != nil { + t.Fatal(err) + } + ns := "not-the-default" + noxuResourceClient := NewNamespacedCustomResourceClient(ns, noxuVersionClient, noxuDefinition) + noxuStatusResourceClient := NewNamespacedCustomResourceStatusClient(ns, noxuVersionClient, noxuDefinition) + + // set .spec.num = 10 and .status.num = 10 + noxuInstance := NewNoxuSubresourceInstance(ns, "foo") + err = unstructured.SetNestedField(noxuInstance.Object, int64(10), "status", "num") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + createdNoxuInstance, err := instantiateCustomResource(t, noxuInstance, noxuResourceClient, noxuDefinition) + if err != nil { + t.Fatalf("unable to create noxu instance: %v", err) + } + + gottenCRD, err := apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get("noxus.mygroup.example.com", metav1.GetOptions{}) + if err != nil { + t.Fatal(err) + } + + // update the crd so that max value of spec.num = 5 and status.num = 10 + gottenCRD.Spec.Validation.OpenAPIV3Schema = &apiextensionsv1beta1.JSONSchemaProps{ + Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{ + "spec": { + Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{ + "num": { + Type: "integer", + Maximum: float64Ptr(5), + }, + }, + }, + "status": { + Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{ + "num": { + Type: "integer", + Maximum: float64Ptr(10), + }, + }, + }, + }, + } + + if _, err = apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Update(gottenCRD); err != nil { + t.Fatal(err) + } + + // update the status with .status.num = 5 + err = unstructured.SetNestedField(createdNoxuInstance.Object, int64(5), "status", "num") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // cr is updated even though spec is invalid + err = wait.Poll(500*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) { + _, err := noxuStatusResourceClient.Update(createdNoxuInstance) + if statusError, isStatus := err.(*apierrors.StatusError); isStatus { + if strings.Contains(statusError.Error(), "is invalid") { + return false, nil + } + } + if err != nil { + return false, err + } + return true, nil + }) + if err != nil { + t.Fatal(err) + } +} + +func TestSubresourcesDiscovery(t *testing.T) { + // enable alpha feature CustomResourceSubresources + defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CustomResourceSubresources, true)() + + stopCh, config, err := testserver.StartDefaultServer() + if err != nil { + t.Fatal(err) + } + defer close(stopCh) + + apiExtensionClient, err := clientset.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + clientPool := dynamic.NewDynamicClientPool(config) + + noxuDefinition := NewNoxuSubresourcesCRD(apiextensionsv1beta1.NamespaceScoped) + _, err = testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool) + if err != nil { + t.Fatal(err) + } + + group := "mygroup.example.com" + version := "v1beta1" + + resources, err := apiExtensionClient.Discovery().ServerResourcesForGroupVersion(group + "/" + version) + if err != nil { + t.Fatal(err) + } + + if len(resources.APIResources) != 3 { + t.Fatalf("Expected exactly the resources \"noxus\", \"noxus/status\" and \"noxus/scale\" in group version %v/%v via discovery, got: %v", group, version, resources.APIResources) + } + + // check discovery info for status + status := resources.APIResources[1] + + if status.Name != "noxus/status" { + t.Fatalf("incorrect status via discovery: expected name: %v, got: %v", "noxus/status", status.Name) + } + + if status.Namespaced != true { + t.Fatalf("incorrect status via discovery: expected namespace: %v, got: %v", true, status.Namespaced) + } + + if status.Kind != "WishIHadChosenNoxu" { + t.Fatalf("incorrect status via discovery: expected kind: %v, got: %v", "WishIHadChosenNoxu", status.Kind) + } + + expectedVerbs := []string{"get", "patch", "update"} + sort.Strings(status.Verbs) + if !reflect.DeepEqual([]string(status.Verbs), expectedVerbs) { + t.Fatalf("incorrect status via discovery: expected: %v, got: %v", expectedVerbs, status.Verbs) + } + + // check discovery info for scale + scale := resources.APIResources[2] + + if scale.Group != autoscaling.GroupName { + t.Fatalf("incorrect scale via discovery: expected group: %v, got: %v", autoscaling.GroupName, scale.Group) + } + + if scale.Version != "v1" { + t.Fatalf("incorrect scale via discovery: expected version: %v, got %v", "v1", scale.Version) + } + + if scale.Name != "noxus/scale" { + t.Fatalf("incorrect scale via discovery: expected name: %v, got: %v", "noxus/scale", scale.Name) + } + + if scale.Namespaced != true { + t.Fatalf("incorrect scale via discovery: expected namespace: %v, got: %v", true, scale.Namespaced) + } + + if scale.Kind != "Scale" { + t.Fatalf("incorrect scale via discovery: expected kind: %v, got: %v", "Scale", scale.Kind) + } + + sort.Strings(scale.Verbs) + if !reflect.DeepEqual([]string(scale.Verbs), expectedVerbs) { + t.Fatalf("incorrect scale via discovery: expected: %v, got: %v", expectedVerbs, scale.Verbs) + } +} + +func TestGeneration(t *testing.T) { + // enable alpha feature CustomResourceSubresources + defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CustomResourceSubresources, true)() + + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() + if err != nil { + t.Fatal(err) + } + defer close(stopCh) + + noxuDefinition := NewNoxuSubresourcesCRD(apiextensionsv1beta1.NamespaceScoped) + noxuVersionClient, err := testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool) + if err != nil { + t.Fatal(err) + } + + ns := "not-the-default" + noxuResourceClient := NewNamespacedCustomResourceClient(ns, noxuVersionClient, noxuDefinition) + noxuStatusResourceClient := NewNamespacedCustomResourceStatusClient(ns, noxuVersionClient, noxuDefinition) + _, err = instantiateCustomResource(t, NewNoxuSubresourceInstance(ns, "foo"), noxuResourceClient, noxuDefinition) + if err != nil { + t.Fatalf("unable to create noxu instance: %v", err) + } + + // .metadata.generation = 1 + gottenNoxuInstance, err := noxuResourceClient.Get("foo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err) + } + if gottenNoxuInstance.GetGeneration() != 1 { + t.Fatalf(".metadata.generation should be 1 after creation") + } + + // .status.num = 20 + err = unstructured.SetNestedField(gottenNoxuInstance.Object, int64(20), "status", "num") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // UpdateStatus does not increment generation + updatedStatusInstance, err := noxuStatusResourceClient.Update(gottenNoxuInstance) + if err != nil { + t.Fatalf("unable to update status: %v", err) + } + if updatedStatusInstance.GetGeneration() != 1 { + t.Fatalf("updating status should not increment .metadata.generation: expected: %v, got: %v", 1, updatedStatusInstance.GetGeneration()) + } + + gottenNoxuInstance, err = noxuResourceClient.Get("foo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err) + } + + // .spec.num = 20 + err = unstructured.SetNestedField(gottenNoxuInstance.Object, int64(20), "spec", "num") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Update increments generation + updatedInstance, err := noxuResourceClient.Update(gottenNoxuInstance) + if err != nil { + t.Fatalf("unable to update instance: %v", err) + } + if updatedInstance.GetGeneration() != 2 { + t.Fatalf("updating spec should increment .metadata.generation: expected: %v, got: %v", 2, updatedStatusInstance.GetGeneration()) + } +} + +func TestSubresourcePatch(t *testing.T) { + // enable alpha feature CustomResourceSubresources + defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CustomResourceSubresources, true)() + + groupResource := schema.GroupResource{ + Group: "mygroup.example.com", + Resource: "noxus", + } + + stopCh, config, err := testserver.StartDefaultServer() + if err != nil { + t.Fatal(err) + } + defer close(stopCh) + + apiExtensionClient, err := clientset.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + clientPool := dynamic.NewDynamicClientPool(config) + + noxuDefinition := NewNoxuSubresourcesCRD(apiextensionsv1beta1.NamespaceScoped) + noxuVersionClient, err := testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool) + if err != nil { + t.Fatal(err) + } + + ns := "not-the-default" + noxuResourceClient := NewNamespacedCustomResourceClient(ns, noxuVersionClient, noxuDefinition) + noxuStatusResourceClient := NewNamespacedCustomResourceStatusClient(ns, noxuVersionClient, noxuDefinition) + noxuScaleResourceClient := NewNamespacedCustomResourceScaleClient(ns, noxuVersionClient, noxuDefinition) + _, err = instantiateCustomResource(t, NewNoxuSubresourceInstance(ns, "foo"), noxuResourceClient, noxuDefinition) + if err != nil { + t.Fatalf("unable to create noxu instance: %v", err) + } + + scaleClient, err := testserver.CreateNewScaleClient(noxuDefinition, config) + if err != nil { + t.Fatal(err) + } + + patch := []byte(`{"spec": {"num":999}, "status": {"num":999}}`) + patchedNoxuInstance, err := noxuStatusResourceClient.Patch("foo", types.MergePatchType, patch) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // .spec.num should remain 10 + specNum, found, err := unstructured.NestedInt64(patchedNoxuInstance.Object, "spec", "num") + if !found || err != nil { + t.Fatalf("unable to get .spec.num") + } + if specNum != 10 { + t.Fatalf(".spec.num: expected: %v, got: %v", 10, specNum) + } + + // .status.num should be 999 + statusNum, found, err := unstructured.NestedInt64(patchedNoxuInstance.Object, "status", "num") + if !found || err != nil { + t.Fatalf("unable to get .status.num") + } + if statusNum != 999 { + t.Fatalf(".status.num: expected: %v, got: %v", 999, statusNum) + } + + // this call waits for the resourceVersion to be reached in the cache before returning. + // We need to do this because the patch gets its initial object from the storage, and the cache serves that. + // If it is out of date, then our initial patch is applied to an old resource version, which conflicts + // and then the updated object shows a conflicting diff, which permanently fails the patch. + // This gives expected stability in the patch without retrying on an known number of conflicts below in the test. + // See https://issue.k8s.io/42644 + _, err = noxuResourceClient.Get("foo", metav1.GetOptions{ResourceVersion: patchedNoxuInstance.GetResourceVersion()}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // no-op patch + _, err = noxuStatusResourceClient.Patch("foo", types.MergePatchType, patch) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // empty patch + _, err = noxuStatusResourceClient.Patch("foo", types.MergePatchType, []byte(`{}`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + patch = []byte(`{"spec": {"replicas":7}, "status": {"replicas":7}}`) + patchedNoxuInstance, err = noxuScaleResourceClient.Patch("foo", types.MergePatchType, patch) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // this call waits for the resourceVersion to be reached in the cache before returning. + // We need to do this because the patch gets its initial object from the storage, and the cache serves that. + // If it is out of date, then our initial patch is applied to an old resource version, which conflicts + // and then the updated object shows a conflicting diff, which permanently fails the patch. + // This gives expected stability in the patch without retrying on an known number of conflicts below in the test. + // See https://issue.k8s.io/42644 + _, err = noxuResourceClient.Get("foo", metav1.GetOptions{ResourceVersion: patchedNoxuInstance.GetResourceVersion()}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Scale.Spec.Replicas = 7 but Scale.Status.Replicas should remain 7 + gottenScale, err := scaleClient.Scales("not-the-default").Get(groupResource, "foo") + if err != nil { + t.Fatal(err) + } + if gottenScale.Spec.Replicas != 7 { + t.Fatalf("Scale.Spec.Replicas: expected: %v, got: %v", 7, gottenScale.Spec.Replicas) + } + if gottenScale.Status.Replicas != 0 { + t.Fatalf("Scale.Status.Replicas: expected: %v, got: %v", 0, gottenScale.Spec.Replicas) + } + + // no-op patch + _, err = noxuScaleResourceClient.Patch("foo", types.MergePatchType, patch) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // empty patch + _, err = noxuScaleResourceClient.Patch("foo", types.MergePatchType, []byte(`{}`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // make sure strategic merge patch is not supported for both status and scale + _, err = noxuStatusResourceClient.Patch("foo", types.StrategicMergePatchType, patch) + if err == nil { + t.Fatalf("unexpected non-error: strategic merge patch is not supported for custom resources") + } + + _, err = noxuScaleResourceClient.Patch("foo", types.StrategicMergePatchType, patch) + if err == nil { + t.Fatalf("unexpected non-error: strategic merge patch is not supported for custom resources") + } +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/resources.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/resources.go index 562281f4c97..9398afd9725 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/resources.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/resources.go @@ -30,7 +30,10 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/watch" "k8s.io/apiserver/pkg/storage/names" + "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" + "k8s.io/client-go/scale" ) const ( @@ -293,3 +296,34 @@ func DeleteCustomResourceDefinition(crd *apiextensionsv1beta1.CustomResourceDefi func GetCustomResourceDefinition(crd *apiextensionsv1beta1.CustomResourceDefinition, apiExtensionsClient clientset.Interface) (*apiextensionsv1beta1.CustomResourceDefinition, error) { return apiExtensionsClient.Apiextensions().CustomResourceDefinitions().Get(crd.Name, metav1.GetOptions{}) } + +func CreateNewScaleClient(crd *apiextensionsv1beta1.CustomResourceDefinition, config *rest.Config) (scale.ScalesGetter, error) { + discoveryClient, err := discovery.NewDiscoveryClientForConfig(config) + if err != nil { + return nil, err + } + groupResource, err := discoveryClient.ServerResourcesForGroupVersion(crd.Spec.Group + "/" + crd.Spec.Version) + if err != nil { + return nil, err + } + + resources := []*discovery.APIGroupResources{ + { + Group: metav1.APIGroup{ + Name: crd.Spec.Group, + Versions: []metav1.GroupVersionForDiscovery{ + {Version: crd.Spec.Version}, + }, + PreferredVersion: metav1.GroupVersionForDiscovery{Version: crd.Spec.Version}, + }, + VersionedResources: map[string][]metav1.APIResource{ + crd.Spec.Version: groupResource.APIResources, + }, + }, + } + + restMapper := discovery.NewRESTMapper(resources, nil) + resolver := scale.NewDiscoveryScaleKindResolver(discoveryClient) + + return scale.NewForConfig(config, restMapper, dynamic.LegacyAPIPathResolverFunc, resolver) +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/start.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/start.go index 35c844fafb7..c0a60bfc70a 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/start.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/start.go @@ -32,6 +32,7 @@ import ( genericapiserver "k8s.io/apiserver/pkg/server" genericapiserveroptions "k8s.io/apiserver/pkg/server/options" "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" ) func DefaultServerConfig() (*extensionsapiserver.Config, error) { @@ -87,11 +88,11 @@ func DefaultServerConfig() (*extensionsapiserver.Config, error) { return config, nil } -func StartServer(config *extensionsapiserver.Config) (chan struct{}, clientset.Interface, dynamic.ClientPool, error) { +func StartServer(config *extensionsapiserver.Config) (chan struct{}, *rest.Config, error) { stopCh := make(chan struct{}) server, err := config.Complete().New(genericapiserver.EmptyDelegate) if err != nil { - return nil, nil, nil, err + return nil, nil, err } go func() { err := server.GenericAPIServer.PrepareRun().Run(stopCh) @@ -123,26 +124,32 @@ func StartServer(config *extensionsapiserver.Config) (chan struct{}, clientset.I }) if err != nil { close(stopCh) + return nil, nil, err + } + + return stopCh, config.GenericConfig.LoopbackClientConfig, nil +} + +func StartDefaultServer() (chan struct{}, *rest.Config, error) { + config, err := DefaultServerConfig() + if err != nil { + return nil, nil, err + } + + return StartServer(config) +} + +func StartDefaultServerWithClients() (chan struct{}, clientset.Interface, dynamic.ClientPool, error) { + stopCh, config, err := StartDefaultServer() + if err != nil { return nil, nil, nil, err } - apiExtensionsClient, err := clientset.NewForConfig(server.GenericAPIServer.LoopbackClientConfig) + apiExtensionsClient, err := clientset.NewForConfig(config) if err != nil { close(stopCh) return nil, nil, nil, err } - bytes, _ := apiExtensionsClient.Discovery().RESTClient().Get().AbsPath("/apis/apiextensions.k8s.io/v1beta1").DoRaw() - fmt.Print(string(bytes)) - - return stopCh, apiExtensionsClient, dynamic.NewDynamicClientPool(server.GenericAPIServer.LoopbackClientConfig), nil -} - -func StartDefaultServer() (chan struct{}, clientset.Interface, dynamic.ClientPool, error) { - config, err := DefaultServerConfig() - if err != nil { - return nil, nil, nil, err - } - - return StartServer(config) + return stopCh, apiExtensionsClient, dynamic.NewDynamicClientPool(config), nil } diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/validation_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/validation_test.go index e30c0ee1e43..3ef47b658d6 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/validation_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/validation_test.go @@ -31,7 +31,7 @@ import ( ) func TestForProperValidationErrors(t *testing.T) { - stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() if err != nil { t.Fatal(err) } @@ -169,7 +169,7 @@ func newNoxuValidationInstance(namespace, name string) *unstructured.Unstructure } func TestCustomResourceValidation(t *testing.T) { - stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() if err != nil { t.Fatal(err) } @@ -190,7 +190,7 @@ func TestCustomResourceValidation(t *testing.T) { } func TestCustomResourceUpdateValidation(t *testing.T) { - stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() if err != nil { t.Fatal(err) } @@ -233,7 +233,7 @@ func TestCustomResourceUpdateValidation(t *testing.T) { } func TestCustomResourceValidationErrors(t *testing.T) { - stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() if err != nil { t.Fatal(err) } @@ -324,7 +324,7 @@ func TestCustomResourceValidationErrors(t *testing.T) { } func TestCRValidationOnCRDUpdate(t *testing.T) { - stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() if err != nil { t.Fatal(err) } @@ -378,7 +378,7 @@ func TestCRValidationOnCRDUpdate(t *testing.T) { } func TestForbiddenFieldsInSchema(t *testing.T) { - stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServer() + stopCh, apiExtensionClient, clientPool, err := testserver.StartDefaultServerWithClients() if err != nil { t.Fatal(err) } diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/yaml_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/yaml_test.go index b6410123896..1782e1a9e27 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/yaml_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/yaml_test.go @@ -24,25 +24,32 @@ import ( "github.com/ghodss/yaml" - apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - "k8s.io/apiextensions-apiserver/test/integration/testserver" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/types" + utilfeature "k8s.io/apiserver/pkg/util/feature" + utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing" + "k8s.io/client-go/dynamic" + + apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "k8s.io/apiextensions-apiserver/pkg/features" + "k8s.io/apiextensions-apiserver/test/integration/testserver" ) func TestYAML(t *testing.T) { - config, err := testserver.DefaultServerConfig() - if err != nil { - t.Fatal(err) - } - - stopCh, apiExtensionClient, clientPool, err := testserver.StartServer(config) + stopCh, config, err := testserver.StartDefaultServer() if err != nil { t.Fatal(err) } defer close(stopCh) + apiExtensionClient, err := clientset.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + clientPool := dynamic.NewDynamicClientPool(config) + noxuDefinition := testserver.NewNoxuCustomResourceDefinition(apiextensionsv1beta1.ClusterScoped) _, err = testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool) if err != nil { @@ -232,7 +239,7 @@ values: Param("watch", "true"). DoRaw() if !errors.IsNotAcceptable(err) { - t.Fatal("expected not acceptable error, got %v (%s)", err, string(result)) + t.Fatalf("expected not acceptable error, got %v (%s)", err, string(result)) } obj, err := decodeYAML(result) if err != nil { @@ -294,7 +301,7 @@ values: t.Fatal(v, ok, err, string(result)) } if obj.GetUID() != uid { - t.Fatal("uid changed: %v vs %v", uid, obj.GetUID()) + t.Fatalf("uid changed: %v vs %v", uid, obj.GetUID()) } } @@ -346,6 +353,179 @@ values: } } +func TestYAMLSubresource(t *testing.T) { + // enable alpha feature CustomResourceSubresources + defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CustomResourceSubresources, true)() + + stopCh, config, err := testserver.StartDefaultServer() + if err != nil { + t.Fatal(err) + } + defer close(stopCh) + + apiExtensionClient, err := clientset.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + clientPool := dynamic.NewDynamicClientPool(config) + + noxuDefinition := NewNoxuSubresourcesCRD(apiextensionsv1beta1.ClusterScoped) + _, err = testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, clientPool) + if err != nil { + t.Fatal(err) + } + + kind := noxuDefinition.Spec.Names.Kind + apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Version + + rest := apiExtensionClient.Discovery().RESTClient() + + uid := types.UID("") + resourceVersion := "" + + // Create + { + yamlBody := []byte(fmt.Sprintf(` +apiVersion: %s +kind: %s +metadata: + name: mytest +spec: + replicas: 3`, apiVersion, kind)) + + result, err := rest.Post(). + SetHeader("Accept", "application/yaml"). + SetHeader("Content-Type", "application/yaml"). + AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural). + Body(yamlBody). + DoRaw() + if err != nil { + t.Fatal(err, string(result)) + } + obj, err := decodeYAML(result) + if err != nil { + t.Fatal(err) + } + if obj.GetName() != "mytest" { + t.Fatalf("expected mytest, got %s", obj.GetName()) + } + if obj.GetAPIVersion() != apiVersion { + t.Fatalf("expected %s, got %s", apiVersion, obj.GetAPIVersion()) + } + if obj.GetKind() != kind { + t.Fatalf("expected %s, got %s", kind, obj.GetKind()) + } + if v, ok, err := unstructured.NestedFloat64(obj.Object, "spec", "replicas"); v != 3 || !ok || err != nil { + t.Fatal(v, ok, err, string(result)) + } + uid = obj.GetUID() + resourceVersion = obj.GetResourceVersion() + } + + // Get at /status + { + result, err := rest.Get(). + SetHeader("Accept", "application/yaml"). + AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural, "mytest", "status"). + DoRaw() + if err != nil { + t.Fatal(err) + } + obj, err := decodeYAML(result) + if err != nil { + t.Fatal(err, string(result)) + } + if obj.GetName() != "mytest" { + t.Fatalf("expected mytest, got %s", obj.GetName()) + } + if obj.GetAPIVersion() != apiVersion { + t.Fatalf("expected %s, got %s", apiVersion, obj.GetAPIVersion()) + } + if obj.GetKind() != kind { + t.Fatalf("expected %s, got %s", kind, obj.GetKind()) + } + if v, ok, err := unstructured.NestedFloat64(obj.Object, "spec", "replicas"); v != 3 || !ok || err != nil { + t.Fatal(v, ok, err, string(result)) + } + } + + // Update at /status + { + yamlBody := []byte(fmt.Sprintf(` +apiVersion: %s +kind: %s +metadata: + name: mytest + uid: %s + resourceVersion: "%s" +spec: + replicas: 5 +status: + replicas: 3`, apiVersion, kind, uid, resourceVersion)) + result, err := rest.Put(). + SetHeader("Accept", "application/yaml"). + SetHeader("Content-Type", "application/yaml"). + AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural, "mytest", "status"). + Body(yamlBody). + DoRaw() + if err != nil { + t.Fatal(err, string(result)) + } + obj, err := decodeYAML(result) + if err != nil { + t.Fatal(err) + } + if obj.GetName() != "mytest" { + t.Fatalf("expected mytest, got %s", obj.GetName()) + } + if obj.GetAPIVersion() != apiVersion { + t.Fatalf("expected %s, got %s", apiVersion, obj.GetAPIVersion()) + } + if obj.GetKind() != kind { + t.Fatalf("expected %s, got %s", kind, obj.GetKind()) + } + if v, ok, err := unstructured.NestedFloat64(obj.Object, "spec", "replicas"); v != 3 || !ok || err != nil { + t.Fatal(v, ok, err, string(result)) + } + if v, ok, err := unstructured.NestedFloat64(obj.Object, "status", "replicas"); v != 3 || !ok || err != nil { + t.Fatal(v, ok, err, string(result)) + } + if obj.GetUID() != uid { + t.Fatalf("uid changed: %v vs %v", uid, obj.GetUID()) + } + } + + // Get at /scale + { + result, err := rest.Get(). + SetHeader("Accept", "application/yaml"). + AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural, "mytest", "scale"). + DoRaw() + if err != nil { + t.Fatal(err) + } + obj, err := decodeYAML(result) + if err != nil { + t.Fatal(err, string(result)) + } + if obj.GetName() != "mytest" { + t.Fatalf("expected mytest, got %s", obj.GetName()) + } + if obj.GetAPIVersion() != "autoscaling/v1" { + t.Fatalf("expected %s, got %s", apiVersion, obj.GetAPIVersion()) + } + if obj.GetKind() != "Scale" { + t.Fatalf("expected %s, got %s", kind, obj.GetKind()) + } + if v, ok, err := unstructured.NestedFloat64(obj.Object, "spec", "replicas"); v != 3 || !ok || err != nil { + t.Fatal(v, ok, err, string(result)) + } + if v, ok, err := unstructured.NestedFloat64(obj.Object, "status", "replicas"); v != 3 || !ok || err != nil { + t.Fatal(v, ok, err, string(result)) + } + } +} + func decodeYAML(data []byte) (*unstructured.Unstructured, error) { retval := &unstructured.Unstructured{Object: map[string]interface{}{}} // ensure this isn't JSON diff --git a/staging/src/k8s.io/client-go/dynamic/client.go b/staging/src/k8s.io/client-go/dynamic/client.go index b93a0ff195f..833e43537b0 100644 --- a/staging/src/k8s.io/client-go/dynamic/client.go +++ b/staging/src/k8s.io/client-go/dynamic/client.go @@ -280,6 +280,7 @@ func (rc *ResourceClient) Watch(opts metav1.ListOptions) (watch.Interface, error Watch() } +// Patch applies the patch and returns the patched resource. func (rc *ResourceClient) Patch(name string, pt types.PatchType, data []byte) (*unstructured.Unstructured, error) { result := new(unstructured.Unstructured) resourceName, subresourceName := rc.parseResourceSubresourceName() diff --git a/staging/src/k8s.io/client-go/scale/client_test.go b/staging/src/k8s.io/client-go/scale/client_test.go index f4f2af3d68c..6301320f3cf 100644 --- a/staging/src/k8s.io/client-go/scale/client_test.go +++ b/staging/src/k8s.io/client-go/scale/client_test.go @@ -99,7 +99,7 @@ func fakeScaleClient(t *testing.T) (ScalesGetter, []schema.GroupResource) { restMapperRes, err := discovery.GetAPIGroupResources(fakeDiscoveryClient) if err != nil { - t.Fatalf("unexpected error while constructing resource list from fake discovery client: %v") + t.Fatalf("unexpected error while constructing resource list from fake discovery client: %v", err) } restMapper := discovery.NewRESTMapper(restMapperRes, apimeta.InterfacesForUnstructured) From 55ce3dedaa51c66dcd52b86f01e687905afe3e94 Mon Sep 17 00:00:00 2001 From: Nikhita Raghunath Date: Mon, 6 Nov 2017 19:15:34 +0530 Subject: [PATCH 3/3] update generated files --- api/openapi-spec/swagger.json | 41 + .../Godeps/Godeps.json | 398 +++++++- .../apiextensions/v1beta1/generated.pb.go | 859 ++++++++++++++---- .../apiextensions/v1beta1/generated.proto | 48 + .../v1beta1/zz_generated.conversion.go | 72 ++ .../v1beta1/zz_generated.deepcopy.go | 84 ++ .../apiextensions/zz_generated.deepcopy.go | 84 ++ .../pkg/apiserver/BUILD | 8 + .../pkg/registry/customresource/BUILD | 37 + .../test/integration/BUILD | 7 + .../test/integration/testserver/BUILD | 3 + 11 files changed, 1481 insertions(+), 160 deletions(-) diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index a16590fd11e..8aed53753fd 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -85415,6 +85415,10 @@ "description": "Scope indicates whether this resource is cluster or namespace scoped. Default is namespaced", "type": "string" }, + "subresources": { + "description": "Subresources describes the subresources for CustomResources This field is alpha-level and should only be sent to servers that enable subresources via the CustomResourceSubresources feature gate.", + "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.CustomResourceSubresources" + }, "validation": { "description": "Validation describes the validation methods for CustomResources", "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.CustomResourceValidation" @@ -85445,6 +85449,43 @@ } } }, + "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.CustomResourceSubresourceScale": { + "description": "CustomResourceSubresourceScale defines how to serve the scale subresource for CustomResources.", + "required": [ + "specReplicasPath", + "statusReplicasPath" + ], + "properties": { + "labelSelectorPath": { + "description": "LabelSelectorPath defines the JSON path inside of a CustomResource that corresponds to Scale.Status.Selector. Only JSON paths without the array notation are allowed. Must be a JSON Path under .status. Must be set to work with HPA. If there is no value under the given path in the CustomResource, the status label selector value in the /scale subresource will default to the empty string.", + "type": "string" + }, + "specReplicasPath": { + "description": "SpecReplicasPath defines the JSON path inside of a CustomResource that corresponds to Scale.Spec.Replicas. Only JSON paths without the array notation are allowed. Must be a JSON Path under .spec. If there is no value under the given path in the CustomResource, the /scale subresource will return an error on GET.", + "type": "string" + }, + "statusReplicasPath": { + "description": "StatusReplicasPath defines the JSON path inside of a CustomResource that corresponds to Scale.Status.Replicas. Only JSON paths without the array notation are allowed. Must be a JSON Path under .status. If there is no value under the given path in the CustomResource, the status replica value in the /scale subresource will default to 0.", + "type": "string" + } + } + }, + "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.CustomResourceSubresourceStatus": { + "description": "CustomResourceSubresourceStatus defines how to serve the status subresource for CustomResources. Status is represented by the `.status` JSON path inside of a CustomResource. When set, * exposes a /status subresource for the custom resource * PUT requests to the /status subresource take a custom resource object, and ignore changes to anything except the status stanza * PUT/POST/PATCH requests to the custom resource ignore changes to the status stanza" + }, + "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.CustomResourceSubresources": { + "description": "CustomResourceSubresources defines the status and scale subresources for CustomResources.", + "properties": { + "scale": { + "description": "Scale denotes the scale subresource for CustomResources", + "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.CustomResourceSubresourceScale" + }, + "status": { + "description": "Status denotes the status subresource for CustomResources", + "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.CustomResourceSubresourceStatus" + } + } + }, "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.CustomResourceValidation": { "description": "CustomResourceValidation is a list of validation methods for CustomResources.", "properties": { diff --git a/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json b/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json index 801ecbcf2d8..89ab4c3ed70 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json +++ b/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json @@ -30,6 +30,22 @@ "ImportPath": "github.com/beorn7/perks/quantile", "Rev": "3ac7bf7a47d159a033b107610db8a1b6575507a4" }, + { + "ImportPath": "github.com/cockroachdb/cmux", + "Rev": "112f0506e7743d64a6eb8fedbcff13d9979bbf92" + }, + { + "ImportPath": "github.com/coreos/bbolt", + "Rev": "48ea1b39c25fc1bab3506fbc712ecbaa842c4d2d" + }, + { + "ImportPath": "github.com/coreos/etcd/alarm", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/auth", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, { "ImportPath": "github.com/coreos/etcd/auth/authpb", "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" @@ -42,26 +58,214 @@ "ImportPath": "github.com/coreos/etcd/clientv3", "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" }, + { + "ImportPath": "github.com/coreos/etcd/clientv3/concurrency", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/clientv3/namespace", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/clientv3/naming", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/compactor", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/discovery", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/embed", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/error", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/etcdserver", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/etcdserver/api", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/etcdserver/api/etcdhttp", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/etcdserver/api/v2http", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/etcdserver/api/v2http/httptypes", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/etcdserver/api/v3client", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/etcdserver/api/v3election", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/etcdserver/api/v3election/v3electionpb", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/etcdserver/api/v3election/v3electionpb/gw", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/etcdserver/api/v3lock", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/etcdserver/api/v3lock/v3lockpb", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/etcdserver/api/v3lock/v3lockpb/gw", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/etcdserver/api/v3rpc", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, { "ImportPath": "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes", "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" }, + { + "ImportPath": "github.com/coreos/etcd/etcdserver/auth", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, { "ImportPath": "github.com/coreos/etcd/etcdserver/etcdserverpb", "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" }, + { + "ImportPath": "github.com/coreos/etcd/etcdserver/etcdserverpb/gw", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/etcdserver/membership", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/etcdserver/stats", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/integration", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/lease", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/lease/leasehttp", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/lease/leasepb", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/mvcc", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/mvcc/backend", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, { "ImportPath": "github.com/coreos/etcd/mvcc/mvccpb", "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" }, + { + "ImportPath": "github.com/coreos/etcd/pkg/adt", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/pkg/contention", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/pkg/cors", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/pkg/cpuutil", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/pkg/crc", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/pkg/debugutil", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/pkg/fileutil", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/pkg/httputil", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/pkg/idutil", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/pkg/ioutil", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/pkg/logutil", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/pkg/monotime", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/pkg/netutil", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, { "ImportPath": "github.com/coreos/etcd/pkg/pathutil", "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" }, + { + "ImportPath": "github.com/coreos/etcd/pkg/pbutil", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/pkg/runtime", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/pkg/schedule", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, { "ImportPath": "github.com/coreos/etcd/pkg/srv", "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" }, + { + "ImportPath": "github.com/coreos/etcd/pkg/testutil", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, { "ImportPath": "github.com/coreos/etcd/pkg/tlsutil", "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" @@ -74,10 +278,58 @@ "ImportPath": "github.com/coreos/etcd/pkg/types", "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" }, + { + "ImportPath": "github.com/coreos/etcd/pkg/wait", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/proxy/grpcproxy", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/proxy/grpcproxy/adapter", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/proxy/grpcproxy/cache", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/raft", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/raft/raftpb", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/rafthttp", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/snap", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/snap/snappb", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/store", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, { "ImportPath": "github.com/coreos/etcd/version", "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" }, + { + "ImportPath": "github.com/coreos/etcd/wal", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, + { + "ImportPath": "github.com/coreos/etcd/wal/walpb", + "Rev": "95a726a27e09030f9ccbd9982a1508f5a6d25ada" + }, { "ImportPath": "github.com/coreos/go-semver/semver", "Rev": "568e959cd89871e61434c1143528d9162da89ef2" @@ -86,10 +338,22 @@ "ImportPath": "github.com/coreos/go-systemd/daemon", "Rev": "48702e0da86bd25e76cfef347e2adeb434a0d0a6" }, + { + "ImportPath": "github.com/coreos/go-systemd/journal", + "Rev": "48702e0da86bd25e76cfef347e2adeb434a0d0a6" + }, + { + "ImportPath": "github.com/coreos/pkg/capnslog", + "Rev": "fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8" + }, { "ImportPath": "github.com/davecgh/go-spew/spew", "Rev": "782f4967f2dc4564575ca782fe2d04090b5faca8" }, + { + "ImportPath": "github.com/dgrijalva/jwt-go", + "Rev": "01aeca54ebda6e0fbfafd0a524d234159c05ec20" + }, { "ImportPath": "github.com/elazarl/go-bindata-assetfs", "Rev": "3dcc96556217539f50599357fb481ac0dc7439b9" @@ -152,7 +416,7 @@ }, { "ImportPath": "github.com/go-openapi/validate", - "Rev": "deaf2c9013bc1a7f4c774662259a506ba874d80f" + "Rev": "d509235108fcf6ab4913d2dcb3a2260c0db2108e" }, { "ImportPath": "github.com/gogo/protobuf/proto", @@ -166,6 +430,14 @@ "ImportPath": "github.com/golang/glog", "Rev": "44145f04b68cf362d9c4df2182967c2275eaefed" }, + { + "ImportPath": "github.com/golang/groupcache/lru", + "Rev": "02826c3e79038b59d737d3b1c0a1d937f71a4433" + }, + { + "ImportPath": "github.com/golang/protobuf/jsonpb", + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" + }, { "ImportPath": "github.com/golang/protobuf/proto", "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" @@ -186,10 +458,18 @@ "ImportPath": "github.com/golang/protobuf/ptypes/duration", "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" }, + { + "ImportPath": "github.com/golang/protobuf/ptypes/struct", + "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" + }, { "ImportPath": "github.com/golang/protobuf/ptypes/timestamp", "Rev": "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" }, + { + "ImportPath": "github.com/google/btree", + "Rev": "7d79101e329e5a3adf994758c578dab82b90c017" + }, { "ImportPath": "github.com/google/gofuzz", "Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c" @@ -206,6 +486,22 @@ "ImportPath": "github.com/googleapis/gnostic/extensions", "Rev": "0c5108395e2debce0d731cf0287ddf7242066aba" }, + { + "ImportPath": "github.com/grpc-ecosystem/go-grpc-prometheus", + "Rev": "2500245aa6110c562d17020fb31a2c133d737799" + }, + { + "ImportPath": "github.com/grpc-ecosystem/grpc-gateway/runtime", + "Rev": "8cc3a55af3bcf171a1c23a90c4df9cf591706104" + }, + { + "ImportPath": "github.com/grpc-ecosystem/grpc-gateway/runtime/internal", + "Rev": "8cc3a55af3bcf171a1c23a90c4df9cf591706104" + }, + { + "ImportPath": "github.com/grpc-ecosystem/grpc-gateway/utilities", + "Rev": "8cc3a55af3bcf171a1c23a90c4df9cf591706104" + }, { "ImportPath": "github.com/hashicorp/golang-lru", "Rev": "a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4" @@ -226,6 +522,10 @@ "ImportPath": "github.com/inconshreveable/mousetrap", "Rev": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" }, + { + "ImportPath": "github.com/jonboulle/clockwork", + "Rev": "72f9bd7c4e0c2a40055ab3d0f09654f730cce982" + }, { "ImportPath": "github.com/json-iterator/go", "Rev": "13f86432b882000a51c6e610c620974462691a97" @@ -306,6 +606,18 @@ "ImportPath": "github.com/ugorji/go/codec", "Rev": "ded73eae5db7e7a0ef6f55aace87a2873c5d2b74" }, + { + "ImportPath": "github.com/xiang90/probing", + "Rev": "07dd2e8dfe18522e9c447ba95f2fe95262f63bb2" + }, + { + "ImportPath": "golang.org/x/crypto/bcrypt", + "Rev": "81e90905daefcd6fd217b62423c0908922eadb30" + }, + { + "ImportPath": "golang.org/x/crypto/blowfish", + "Rev": "81e90905daefcd6fd217b62423c0908922eadb30" + }, { "ImportPath": "golang.org/x/crypto/ssh/terminal", "Rev": "81e90905daefcd6fd217b62423c0908922eadb30" @@ -1058,6 +1370,10 @@ "ImportPath": "k8s.io/apiserver/pkg/registry/rest", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apiserver/pkg/registry/rest/resttest", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apiserver/pkg/server", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1106,10 +1422,18 @@ "ImportPath": "k8s.io/apiserver/pkg/storage/etcd", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apiserver/pkg/storage/etcd/etcdtest", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apiserver/pkg/storage/etcd/metrics", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apiserver/pkg/storage/etcd/testing/testingcert", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apiserver/pkg/storage/etcd/util", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1134,6 +1458,10 @@ "ImportPath": "k8s.io/apiserver/pkg/storage/storagebackend/factory", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apiserver/pkg/storage/testing", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apiserver/pkg/storage/value", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1182,6 +1510,10 @@ "ImportPath": "k8s.io/client-go/discovery", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/dynamic", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/informers", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1570,6 +1902,34 @@ "ImportPath": "k8s.io/client-go/rest/watch", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/scale/scheme", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/client-go/scale/scheme/appsint", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/client-go/scale/scheme/appsv1beta1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/client-go/scale/scheme/appsv1beta2", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/client-go/scale/scheme/autoscalingv1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/client-go/scale/scheme/extensionsint", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/client-go/scale/scheme/extensionsv1beta1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/testing", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1670,6 +2030,10 @@ "ImportPath": "k8s.io/apimachinery/pkg/api/meta", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apimachinery/pkg/api/resource", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apimachinery/pkg/api/testing/fuzzer", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1690,6 +2054,10 @@ "ImportPath": "k8s.io/apimachinery/pkg/apimachinery/registered", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/internalversion", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1734,10 +2102,18 @@ "ImportPath": "k8s.io/apimachinery/pkg/types", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apimachinery/pkg/util/diff", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apimachinery/pkg/util/errors", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apimachinery/pkg/util/intstr", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apimachinery/pkg/util/json", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1802,6 +2178,10 @@ "ImportPath": "k8s.io/apiserver/pkg/registry/generic/registry", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apiserver/pkg/registry/generic/testing", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apiserver/pkg/registry/rest", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1826,6 +2206,10 @@ "ImportPath": "k8s.io/apiserver/pkg/storage/errors", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apiserver/pkg/storage/etcd/testing", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apiserver/pkg/storage/names", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1838,6 +2222,10 @@ "ImportPath": "k8s.io/apiserver/pkg/util/feature", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apiserver/pkg/util/feature/testing", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apiserver/pkg/util/logs", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1858,6 +2246,14 @@ "ImportPath": "k8s.io/client-go/rest", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/scale", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/client-go/scale/scheme/autoscalingv1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/testing", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.pb.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.pb.go index 488f5356022..176a3ce4998 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.pb.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.pb.go @@ -31,6 +31,9 @@ limitations under the License. CustomResourceDefinitionNames CustomResourceDefinitionSpec CustomResourceDefinitionStatus + CustomResourceSubresourceScale + CustomResourceSubresourceStatus + CustomResourceSubresources CustomResourceValidation ExternalDocumentation JSON @@ -99,36 +102,54 @@ func (*CustomResourceDefinitionStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{5} } +func (m *CustomResourceSubresourceScale) Reset() { *m = CustomResourceSubresourceScale{} } +func (*CustomResourceSubresourceScale) ProtoMessage() {} +func (*CustomResourceSubresourceScale) Descriptor() ([]byte, []int) { + return fileDescriptorGenerated, []int{6} +} + +func (m *CustomResourceSubresourceStatus) Reset() { *m = CustomResourceSubresourceStatus{} } +func (*CustomResourceSubresourceStatus) ProtoMessage() {} +func (*CustomResourceSubresourceStatus) Descriptor() ([]byte, []int) { + return fileDescriptorGenerated, []int{7} +} + +func (m *CustomResourceSubresources) Reset() { *m = CustomResourceSubresources{} } +func (*CustomResourceSubresources) ProtoMessage() {} +func (*CustomResourceSubresources) Descriptor() ([]byte, []int) { + return fileDescriptorGenerated, []int{8} +} + func (m *CustomResourceValidation) Reset() { *m = CustomResourceValidation{} } func (*CustomResourceValidation) ProtoMessage() {} func (*CustomResourceValidation) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{6} + return fileDescriptorGenerated, []int{9} } func (m *ExternalDocumentation) Reset() { *m = ExternalDocumentation{} } func (*ExternalDocumentation) ProtoMessage() {} -func (*ExternalDocumentation) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{7} } +func (*ExternalDocumentation) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{10} } func (m *JSON) Reset() { *m = JSON{} } func (*JSON) ProtoMessage() {} -func (*JSON) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{8} } +func (*JSON) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{11} } func (m *JSONSchemaProps) Reset() { *m = JSONSchemaProps{} } func (*JSONSchemaProps) ProtoMessage() {} -func (*JSONSchemaProps) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{9} } +func (*JSONSchemaProps) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{12} } func (m *JSONSchemaPropsOrArray) Reset() { *m = JSONSchemaPropsOrArray{} } func (*JSONSchemaPropsOrArray) ProtoMessage() {} -func (*JSONSchemaPropsOrArray) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{10} } +func (*JSONSchemaPropsOrArray) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{13} } func (m *JSONSchemaPropsOrBool) Reset() { *m = JSONSchemaPropsOrBool{} } func (*JSONSchemaPropsOrBool) ProtoMessage() {} -func (*JSONSchemaPropsOrBool) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{11} } +func (*JSONSchemaPropsOrBool) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{14} } func (m *JSONSchemaPropsOrStringArray) Reset() { *m = JSONSchemaPropsOrStringArray{} } func (*JSONSchemaPropsOrStringArray) ProtoMessage() {} func (*JSONSchemaPropsOrStringArray) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{12} + return fileDescriptorGenerated, []int{15} } func init() { @@ -138,6 +159,9 @@ func init() { proto.RegisterType((*CustomResourceDefinitionNames)(nil), "k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1beta1.CustomResourceDefinitionNames") proto.RegisterType((*CustomResourceDefinitionSpec)(nil), "k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1beta1.CustomResourceDefinitionSpec") proto.RegisterType((*CustomResourceDefinitionStatus)(nil), "k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1beta1.CustomResourceDefinitionStatus") + proto.RegisterType((*CustomResourceSubresourceScale)(nil), "k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1beta1.CustomResourceSubresourceScale") + proto.RegisterType((*CustomResourceSubresourceStatus)(nil), "k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1beta1.CustomResourceSubresourceStatus") + proto.RegisterType((*CustomResourceSubresources)(nil), "k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1beta1.CustomResourceSubresources") proto.RegisterType((*CustomResourceValidation)(nil), "k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1beta1.CustomResourceValidation") proto.RegisterType((*ExternalDocumentation)(nil), "k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1beta1.ExternalDocumentation") proto.RegisterType((*JSON)(nil), "k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1beta1.JSON") @@ -362,6 +386,16 @@ func (m *CustomResourceDefinitionSpec) MarshalTo(dAtA []byte) (int, error) { } i += n7 } + if m.Subresources != nil { + dAtA[i] = 0x32 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Subresources.Size())) + n8, err := m.Subresources.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n8 + } return i, nil } @@ -395,11 +429,99 @@ func (m *CustomResourceDefinitionStatus) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.AcceptedNames.Size())) - n8, err := m.AcceptedNames.MarshalTo(dAtA[i:]) + n9, err := m.AcceptedNames.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n8 + i += n9 + return i, nil +} + +func (m *CustomResourceSubresourceScale) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CustomResourceSubresourceScale) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.SpecReplicasPath))) + i += copy(dAtA[i:], m.SpecReplicasPath) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.StatusReplicasPath))) + i += copy(dAtA[i:], m.StatusReplicasPath) + if m.LabelSelectorPath != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.LabelSelectorPath))) + i += copy(dAtA[i:], *m.LabelSelectorPath) + } + return i, nil +} + +func (m *CustomResourceSubresourceStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CustomResourceSubresourceStatus) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *CustomResourceSubresources) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CustomResourceSubresources) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Status != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) + n10, err := m.Status.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n10 + } + if m.Scale != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Scale.Size())) + n11, err := m.Scale.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n11 + } return i, nil } @@ -422,11 +544,11 @@ func (m *CustomResourceValidation) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.OpenAPIV3Schema.Size())) - n9, err := m.OpenAPIV3Schema.MarshalTo(dAtA[i:]) + n12, err := m.OpenAPIV3Schema.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n9 + i += n12 } return i, nil } @@ -530,11 +652,11 @@ func (m *JSONSchemaProps) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x42 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Default.Size())) - n10, err := m.Default.MarshalTo(dAtA[i:]) + n13, err := m.Default.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n10 + i += n13 } if m.Maximum != nil { dAtA[i] = 0x49 @@ -658,11 +780,11 @@ func (m *JSONSchemaProps) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Items.Size())) - n11, err := m.Items.MarshalTo(dAtA[i:]) + n14, err := m.Items.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n11 + i += n14 } if len(m.AllOf) > 0 { for _, msg := range m.AllOf { @@ -712,11 +834,11 @@ func (m *JSONSchemaProps) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Not.Size())) - n12, err := m.Not.MarshalTo(dAtA[i:]) + n15, err := m.Not.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n12 + i += n15 } if len(m.Properties) > 0 { keysForProperties := make([]string, 0, len(m.Properties)) @@ -744,11 +866,11 @@ func (m *JSONSchemaProps) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64((&v).Size())) - n13, err := (&v).MarshalTo(dAtA[i:]) + n16, err := (&v).MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n13 + i += n16 } } if m.AdditionalProperties != nil { @@ -757,11 +879,11 @@ func (m *JSONSchemaProps) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.AdditionalProperties.Size())) - n14, err := m.AdditionalProperties.MarshalTo(dAtA[i:]) + n17, err := m.AdditionalProperties.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n14 + i += n17 } if len(m.PatternProperties) > 0 { keysForPatternProperties := make([]string, 0, len(m.PatternProperties)) @@ -789,11 +911,11 @@ func (m *JSONSchemaProps) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64((&v).Size())) - n15, err := (&v).MarshalTo(dAtA[i:]) + n18, err := (&v).MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n15 + i += n18 } } if len(m.Dependencies) > 0 { @@ -822,11 +944,11 @@ func (m *JSONSchemaProps) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64((&v).Size())) - n16, err := (&v).MarshalTo(dAtA[i:]) + n19, err := (&v).MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n16 + i += n19 } } if m.AdditionalItems != nil { @@ -835,11 +957,11 @@ func (m *JSONSchemaProps) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x2 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.AdditionalItems.Size())) - n17, err := m.AdditionalItems.MarshalTo(dAtA[i:]) + n20, err := m.AdditionalItems.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n17 + i += n20 } if len(m.Definitions) > 0 { keysForDefinitions := make([]string, 0, len(m.Definitions)) @@ -867,11 +989,11 @@ func (m *JSONSchemaProps) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64((&v).Size())) - n18, err := (&v).MarshalTo(dAtA[i:]) + n21, err := (&v).MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n18 + i += n21 } } if m.ExternalDocs != nil { @@ -880,11 +1002,11 @@ func (m *JSONSchemaProps) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x2 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ExternalDocs.Size())) - n19, err := m.ExternalDocs.MarshalTo(dAtA[i:]) + n22, err := m.ExternalDocs.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n19 + i += n22 } if m.Example != nil { dAtA[i] = 0xa2 @@ -892,11 +1014,11 @@ func (m *JSONSchemaProps) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x2 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Example.Size())) - n20, err := m.Example.MarshalTo(dAtA[i:]) + n23, err := m.Example.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n20 + i += n23 } return i, nil } @@ -920,11 +1042,11 @@ func (m *JSONSchemaPropsOrArray) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Schema.Size())) - n21, err := m.Schema.MarshalTo(dAtA[i:]) + n24, err := m.Schema.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n21 + i += n24 } if len(m.JSONSchemas) > 0 { for _, msg := range m.JSONSchemas { @@ -968,11 +1090,11 @@ func (m *JSONSchemaPropsOrBool) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Schema.Size())) - n22, err := m.Schema.MarshalTo(dAtA[i:]) + n25, err := m.Schema.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n22 + i += n25 } return i, nil } @@ -996,11 +1118,11 @@ func (m *JSONSchemaPropsOrStringArray) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Schema.Size())) - n23, err := m.Schema.MarshalTo(dAtA[i:]) + n26, err := m.Schema.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n23 + i += n26 } if len(m.Property) > 0 { for _, s := range m.Property { @@ -1124,6 +1246,10 @@ func (m *CustomResourceDefinitionSpec) Size() (n int) { l = m.Validation.Size() n += 1 + l + sovGenerated(uint64(l)) } + if m.Subresources != nil { + l = m.Subresources.Size() + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -1141,6 +1267,40 @@ func (m *CustomResourceDefinitionStatus) Size() (n int) { return n } +func (m *CustomResourceSubresourceScale) Size() (n int) { + var l int + _ = l + l = len(m.SpecReplicasPath) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.StatusReplicasPath) + n += 1 + l + sovGenerated(uint64(l)) + if m.LabelSelectorPath != nil { + l = len(*m.LabelSelectorPath) + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *CustomResourceSubresourceStatus) Size() (n int) { + var l int + _ = l + return n +} + +func (m *CustomResourceSubresources) Size() (n int) { + var l int + _ = l + if m.Status != nil { + l = m.Status.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.Scale != nil { + l = m.Scale.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + func (m *CustomResourceValidation) Size() (n int) { var l int _ = l @@ -1436,6 +1596,7 @@ func (this *CustomResourceDefinitionSpec) String() string { `Names:` + strings.Replace(strings.Replace(this.Names.String(), "CustomResourceDefinitionNames", "CustomResourceDefinitionNames", 1), `&`, ``, 1) + `,`, `Scope:` + fmt.Sprintf("%v", this.Scope) + `,`, `Validation:` + strings.Replace(fmt.Sprintf("%v", this.Validation), "CustomResourceValidation", "CustomResourceValidation", 1) + `,`, + `Subresources:` + strings.Replace(fmt.Sprintf("%v", this.Subresources), "CustomResourceSubresources", "CustomResourceSubresources", 1) + `,`, `}`, }, "") return s @@ -1451,6 +1612,38 @@ func (this *CustomResourceDefinitionStatus) String() string { }, "") return s } +func (this *CustomResourceSubresourceScale) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&CustomResourceSubresourceScale{`, + `SpecReplicasPath:` + fmt.Sprintf("%v", this.SpecReplicasPath) + `,`, + `StatusReplicasPath:` + fmt.Sprintf("%v", this.StatusReplicasPath) + `,`, + `LabelSelectorPath:` + valueToStringGenerated(this.LabelSelectorPath) + `,`, + `}`, + }, "") + return s +} +func (this *CustomResourceSubresourceStatus) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&CustomResourceSubresourceStatus{`, + `}`, + }, "") + return s +} +func (this *CustomResourceSubresources) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&CustomResourceSubresources{`, + `Status:` + strings.Replace(fmt.Sprintf("%v", this.Status), "CustomResourceSubresourceStatus", "CustomResourceSubresourceStatus", 1) + `,`, + `Scale:` + strings.Replace(fmt.Sprintf("%v", this.Scale), "CustomResourceSubresourceScale", "CustomResourceSubresourceScale", 1) + `,`, + `}`, + }, "") + return s +} func (this *CustomResourceValidation) String() string { if this == nil { return "nil" @@ -2429,6 +2622,39 @@ func (m *CustomResourceDefinitionSpec) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Subresources", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Subresources == nil { + m.Subresources = &CustomResourceSubresources{} + } + if err := m.Subresources.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -2561,6 +2787,310 @@ func (m *CustomResourceDefinitionStatus) Unmarshal(dAtA []byte) error { } return nil } +func (m *CustomResourceSubresourceScale) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CustomResourceSubresourceScale: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CustomResourceSubresourceScale: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SpecReplicasPath", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SpecReplicasPath = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StatusReplicasPath", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.StatusReplicasPath = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LabelSelectorPath", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.LabelSelectorPath = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CustomResourceSubresourceStatus) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CustomResourceSubresourceStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CustomResourceSubresourceStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CustomResourceSubresources) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CustomResourceSubresources: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CustomResourceSubresources: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Status == nil { + m.Status = &CustomResourceSubresourceStatus{} + } + if err := m.Status.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Scale", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Scale == nil { + m.Scale = &CustomResourceSubresourceScale{} + } + if err := m.Scale.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *CustomResourceValidation) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -4660,125 +5190,136 @@ func init() { } var fileDescriptorGenerated = []byte{ - // 1917 bytes of a gzipped FileDescriptorProto + // 2081 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x58, 0xdb, 0x6f, 0x5b, 0x49, - 0x19, 0xcf, 0xd8, 0xb9, 0x4e, 0x92, 0x4d, 0x32, 0x6d, 0xca, 0x69, 0x68, 0xed, 0xd4, 0xcb, 0xae, - 0x02, 0x6c, 0x6d, 0xba, 0x17, 0x76, 0x41, 0xe2, 0x21, 0x6e, 0x02, 0x2a, 0x34, 0x4d, 0x35, 0x69, - 0x8b, 0xc4, 0x2e, 0xec, 0x4e, 0x7c, 0xc6, 0xce, 0x34, 0xe7, 0xcc, 0x39, 0x3d, 0x33, 0xc7, 0x8d, - 0x25, 0x90, 0x40, 0x68, 0x85, 0x84, 0x04, 0x42, 0xd0, 0x17, 0x24, 0x9e, 0x78, 0x44, 0x08, 0x24, - 0xe0, 0x91, 0x3f, 0xa0, 0x8f, 0x2b, 0xf1, 0xb2, 0x4f, 0x16, 0x35, 0xff, 0x02, 0xbc, 0xe4, 0x09, - 0xcd, 0xe5, 0xdc, 0xec, 0x64, 0xb7, 0xd2, 0xda, 0xdb, 0x37, 0x9f, 0xef, 0xf6, 0xfb, 0xcd, 0x37, - 0xdf, 0x7c, 0xf3, 0x8d, 0x61, 0xfb, 0xf8, 0x1d, 0x51, 0x67, 0x41, 0xe3, 0x38, 0x3e, 0xa4, 0x11, - 0xa7, 0x92, 0x8a, 0x46, 0x97, 0x72, 0x37, 0x88, 0x1a, 0x56, 0x41, 0x42, 0x46, 0x4f, 0x24, 0xe5, - 0x82, 0x05, 0x5c, 0x5c, 0x27, 0x21, 0x13, 0x34, 0xea, 0xd2, 0xa8, 0x11, 0x1e, 0x77, 0x94, 0x4e, - 0x14, 0x0d, 0x1a, 0xdd, 0x1b, 0x87, 0x54, 0x92, 0x1b, 0x8d, 0x0e, 0xe5, 0x34, 0x22, 0x92, 0xba, - 0xf5, 0x30, 0x0a, 0x64, 0x80, 0xbe, 0x65, 0xc2, 0xd5, 0x0b, 0xd6, 0xef, 0xa7, 0xe1, 0xea, 0xe1, - 0x71, 0x47, 0xe9, 0x44, 0xd1, 0xa0, 0x6e, 0xc3, 0x6d, 0x5c, 0xef, 0x30, 0x79, 0x14, 0x1f, 0xd6, - 0x5b, 0x81, 0xdf, 0xe8, 0x04, 0x9d, 0xa0, 0xa1, 0xa3, 0x1e, 0xc6, 0x6d, 0xfd, 0xa5, 0x3f, 0xf4, - 0x2f, 0x83, 0xb6, 0xf1, 0x66, 0x46, 0xde, 0x27, 0xad, 0x23, 0xc6, 0x69, 0xd4, 0xcb, 0x18, 0xfb, - 0x54, 0x92, 0x46, 0x77, 0x84, 0xe3, 0x46, 0xe3, 0x3c, 0xaf, 0x28, 0xe6, 0x92, 0xf9, 0x74, 0xc4, - 0xe1, 0xeb, 0x9f, 0xe6, 0x20, 0x5a, 0x47, 0xd4, 0x27, 0x23, 0x7e, 0x6f, 0x9c, 0xe7, 0x17, 0x4b, - 0xe6, 0x35, 0x18, 0x97, 0x42, 0x46, 0xc3, 0x4e, 0xb5, 0x9f, 0x97, 0xa1, 0x73, 0x33, 0x16, 0x32, - 0xf0, 0x31, 0x15, 0x41, 0x1c, 0xb5, 0xe8, 0x0e, 0x6d, 0x33, 0xce, 0x24, 0x0b, 0x38, 0xfa, 0x00, - 0xce, 0xab, 0x55, 0xb9, 0x44, 0x12, 0x07, 0x6c, 0x82, 0xad, 0xc5, 0xd7, 0xbf, 0x56, 0xcf, 0x32, - 0x9e, 0x82, 0x64, 0x69, 0x56, 0xd6, 0xf5, 0xee, 0x8d, 0xfa, 0xfe, 0xe1, 0x43, 0xda, 0x92, 0x7b, - 0x54, 0x92, 0x26, 0x7a, 0xda, 0xaf, 0x4e, 0x0d, 0xfa, 0x55, 0x98, 0xc9, 0x70, 0x1a, 0x15, 0xfd, - 0x04, 0x4e, 0x8b, 0x90, 0xb6, 0x9c, 0x92, 0x8e, 0xfe, 0x6e, 0xfd, 0x33, 0xed, 0x67, 0xfd, 0xbc, - 0x85, 0x1c, 0x84, 0xb4, 0xd5, 0x5c, 0xb2, 0x44, 0xa6, 0xd5, 0x17, 0xd6, 0xb0, 0xe8, 0x43, 0x00, - 0x67, 0x85, 0x24, 0x32, 0x16, 0x4e, 0x59, 0x33, 0xf8, 0xe1, 0xa4, 0x18, 0x68, 0x90, 0xe6, 0x4b, - 0x96, 0xc3, 0xac, 0xf9, 0xc6, 0x16, 0xbc, 0xf6, 0xdf, 0x12, 0xbc, 0x76, 0x9e, 0xeb, 0xcd, 0x80, - 0xbb, 0x66, 0x3b, 0x6e, 0xc1, 0x69, 0xd9, 0x0b, 0xa9, 0xde, 0x8a, 0x85, 0xe6, 0x5b, 0xc9, 0x7a, - 0xee, 0xf5, 0x42, 0x7a, 0xda, 0xaf, 0xbe, 0xf2, 0xa9, 0x01, 0x94, 0x21, 0xd6, 0x21, 0xd0, 0x37, - 0xd2, 0x75, 0x97, 0x74, 0xb0, 0x6b, 0x45, 0x62, 0xa7, 0xfd, 0xea, 0x4a, 0xea, 0x56, 0xe4, 0x8a, - 0xba, 0x10, 0x79, 0x44, 0xc8, 0x7b, 0x11, 0xe1, 0xc2, 0x84, 0x65, 0x3e, 0xb5, 0xe9, 0xfb, 0xca, - 0xf3, 0x95, 0x87, 0xf2, 0x68, 0x6e, 0x58, 0x48, 0x74, 0x7b, 0x24, 0x1a, 0x3e, 0x03, 0x01, 0xbd, - 0x0a, 0x67, 0x23, 0x4a, 0x44, 0xc0, 0x9d, 0x69, 0x4d, 0x39, 0xcd, 0x25, 0xd6, 0x52, 0x6c, 0xb5, - 0xe8, 0xcb, 0x70, 0xce, 0xa7, 0x42, 0x90, 0x0e, 0x75, 0x66, 0xb4, 0xe1, 0x8a, 0x35, 0x9c, 0xdb, - 0x33, 0x62, 0x9c, 0xe8, 0x6b, 0xa7, 0x00, 0x5e, 0x39, 0x2f, 0x6b, 0xb7, 0x99, 0x90, 0xe8, 0xbd, - 0x91, 0x03, 0x50, 0x7f, 0xbe, 0x15, 0x2a, 0x6f, 0x5d, 0xfe, 0xab, 0x16, 0x7c, 0x3e, 0x91, 0xe4, - 0x8a, 0xff, 0xc7, 0x70, 0x86, 0x49, 0xea, 0xab, 0x3d, 0x28, 0x6f, 0x2d, 0xbe, 0xfe, 0xfd, 0x09, - 0xd5, 0x5e, 0x73, 0xd9, 0x72, 0x98, 0xb9, 0xa5, 0xd0, 0xb0, 0x01, 0xad, 0xfd, 0x0f, 0xc0, 0xab, - 0xe7, 0xb9, 0xdc, 0x21, 0x3e, 0x15, 0x2a, 0xe3, 0xa1, 0x17, 0x47, 0xc4, 0xb3, 0x15, 0x97, 0x66, - 0xfc, 0xae, 0x96, 0x62, 0xab, 0x45, 0xaf, 0xc1, 0x79, 0xc1, 0x78, 0x27, 0xf6, 0x48, 0x64, 0xcb, - 0x29, 0x5d, 0xf5, 0x81, 0x95, 0xe3, 0xd4, 0x02, 0xd5, 0x21, 0x14, 0x47, 0x41, 0x24, 0x35, 0x86, - 0x53, 0xde, 0x2c, 0xab, 0xc8, 0xaa, 0x41, 0x1c, 0xa4, 0x52, 0x9c, 0xb3, 0x40, 0x9b, 0x70, 0xfa, - 0x98, 0x71, 0xd7, 0xee, 0x7a, 0x7a, 0x8a, 0xbf, 0xc7, 0xb8, 0x8b, 0xb5, 0x46, 0xe1, 0x7b, 0x4c, - 0x48, 0x25, 0xb1, 0x5b, 0x5e, 0xc8, 0xba, 0xb6, 0x4c, 0x2d, 0x6a, 0x7f, 0x2b, 0x9f, 0xbf, 0xe9, - 0xaa, 0x35, 0xa0, 0x97, 0xe1, 0x4c, 0x27, 0x0a, 0xe2, 0xd0, 0xae, 0x3a, 0xcd, 0xde, 0x77, 0x94, - 0x10, 0x1b, 0x9d, 0xaa, 0xb2, 0x2e, 0x8d, 0xd4, 0x06, 0xd8, 0x25, 0xa7, 0x55, 0xf6, 0xc0, 0x88, - 0x71, 0xa2, 0x47, 0x3f, 0x03, 0x70, 0x86, 0xdb, 0xc5, 0xaa, 0x12, 0x7a, 0x6f, 0x42, 0xfb, 0xac, - 0xd3, 0x95, 0xd1, 0x35, 0x99, 0x34, 0xc8, 0xe8, 0x4d, 0x38, 0x23, 0x5a, 0x41, 0x48, 0x6d, 0x16, - 0x2b, 0x89, 0xd1, 0x81, 0x12, 0x9e, 0xf6, 0xab, 0xcb, 0x49, 0x38, 0x2d, 0xc0, 0xc6, 0x18, 0xfd, - 0x02, 0x40, 0xd8, 0x25, 0x1e, 0x73, 0x89, 0x8a, 0xaf, 0x73, 0x3b, 0xee, 0x32, 0x7d, 0x90, 0x86, - 0x37, 0x45, 0x90, 0x7d, 0xe3, 0x1c, 0x74, 0xed, 0x5f, 0x25, 0x58, 0xf9, 0xe4, 0xde, 0x8a, 0x9e, - 0x00, 0x08, 0x5b, 0x49, 0xcf, 0x12, 0x0e, 0xd0, 0x67, 0xea, 0x83, 0x09, 0xe5, 0x3a, 0x6d, 0x8e, - 0xd9, 0xfd, 0x96, 0x8a, 0x04, 0xce, 0xf1, 0x40, 0xbf, 0x07, 0x70, 0x99, 0xb4, 0x5a, 0x34, 0x94, - 0xd4, 0x35, 0x25, 0x5f, 0xfa, 0x1c, 0xaa, 0x60, 0xdd, 0xb2, 0x5a, 0xde, 0xce, 0x43, 0xe3, 0x22, - 0x93, 0xda, 0x9f, 0xc0, 0xf0, 0xe5, 0x9f, 0xa5, 0x1f, 0xfd, 0x0a, 0xc0, 0x95, 0x20, 0xa4, 0x7c, - 0xfb, 0xee, 0xad, 0x07, 0x6f, 0x1c, 0xe8, 0x91, 0xc3, 0xf6, 0xc0, 0x3b, 0x9f, 0x91, 0xfa, 0x77, - 0x0f, 0xf6, 0xef, 0x98, 0x80, 0x77, 0xa3, 0x20, 0x14, 0xcd, 0x0b, 0x83, 0x7e, 0x75, 0x65, 0xbf, - 0x08, 0x85, 0x87, 0xb1, 0x6b, 0x3e, 0x5c, 0xdf, 0x3d, 0x91, 0x34, 0xe2, 0xc4, 0xdb, 0x09, 0x5a, - 0xb1, 0x4f, 0xb9, 0x34, 0x44, 0xdf, 0x82, 0x8b, 0x2e, 0x15, 0xad, 0x88, 0x85, 0xba, 0x4a, 0xcd, - 0xa9, 0xbd, 0x60, 0x13, 0xb0, 0xb8, 0x93, 0xa9, 0x70, 0xde, 0x0e, 0x5d, 0x85, 0xe5, 0x38, 0xf2, - 0xec, 0xe9, 0x5d, 0xb4, 0xe6, 0xe5, 0xfb, 0xf8, 0x36, 0x56, 0xf2, 0xda, 0x35, 0x38, 0xad, 0x78, - 0xa2, 0xcb, 0xb0, 0x1c, 0x91, 0xc7, 0x3a, 0xea, 0x52, 0x73, 0x4e, 0x99, 0x60, 0xf2, 0x18, 0x2b, - 0x59, 0xed, 0xcf, 0x57, 0xe0, 0xca, 0xd0, 0x5a, 0xd0, 0x06, 0x2c, 0x31, 0xd7, 0x72, 0x80, 0x36, - 0x68, 0xe9, 0xd6, 0x0e, 0x2e, 0x31, 0x17, 0xbd, 0x0d, 0x67, 0xcd, 0xe8, 0x66, 0x41, 0xab, 0xe9, - 0xa5, 0xab, 0xa5, 0xea, 0x18, 0x66, 0xe1, 0x14, 0x11, 0x6b, 0xae, 0x39, 0xd0, 0xb6, 0x6e, 0x1f, - 0x0b, 0x96, 0x03, 0x6d, 0x63, 0x25, 0x1b, 0x5e, 0xfc, 0xf4, 0x73, 0x2e, 0x7e, 0xd3, 0x8e, 0x12, - 0x33, 0xc5, 0xa6, 0x9a, 0x9b, 0x10, 0x5e, 0x85, 0xb3, 0xed, 0x20, 0xf2, 0x89, 0x74, 0x66, 0x8b, - 0xcd, 0xff, 0xdb, 0x5a, 0x8a, 0xad, 0x56, 0x75, 0x4b, 0xc9, 0xa4, 0x47, 0x9d, 0xb9, 0x62, 0xb7, - 0xbc, 0xa7, 0x84, 0xd8, 0xe8, 0xd0, 0x43, 0x38, 0xe7, 0xd2, 0x36, 0x89, 0x3d, 0xe9, 0xcc, 0xeb, - 0x12, 0xba, 0x39, 0x86, 0x12, 0x6a, 0x2e, 0xaa, 0x76, 0xbb, 0x63, 0xe2, 0xe2, 0x04, 0x00, 0xbd, - 0x02, 0xe7, 0x7c, 0x72, 0xc2, 0xfc, 0xd8, 0x77, 0x16, 0x36, 0xc1, 0x16, 0x30, 0x66, 0x7b, 0x46, - 0x84, 0x13, 0x1d, 0xda, 0x81, 0xab, 0xf4, 0xa4, 0xe5, 0xc5, 0x82, 0x75, 0xa9, 0x55, 0x3a, 0x70, - 0x13, 0x6c, 0xcd, 0x37, 0x1d, 0xbb, 0x84, 0xd5, 0xdd, 0x21, 0x3d, 0x1e, 0xf1, 0xd0, 0x60, 0x8c, - 0x6b, 0xe7, 0xc5, 0x1c, 0x98, 0x11, 0xe1, 0x44, 0x57, 0x04, 0xb3, 0xf6, 0x4b, 0xe7, 0x81, 0x59, - 0xe7, 0x11, 0x0f, 0xf4, 0x55, 0xb8, 0xe0, 0x93, 0x93, 0xdb, 0x94, 0x77, 0xe4, 0x91, 0xb3, 0xbc, - 0x09, 0xb6, 0xca, 0xcd, 0xe5, 0x41, 0xbf, 0xba, 0xb0, 0x97, 0x08, 0x71, 0xa6, 0xd7, 0xc6, 0x8c, - 0x5b, 0xe3, 0x97, 0x72, 0xc6, 0x89, 0x10, 0x67, 0x7a, 0x75, 0x9b, 0x85, 0x44, 0xaa, 0xc3, 0xe5, - 0xac, 0x14, 0x6f, 0xb3, 0xbb, 0x46, 0x8c, 0x13, 0x3d, 0xda, 0x82, 0xf3, 0x3e, 0x39, 0xd1, 0x93, - 0x84, 0xb3, 0xaa, 0xc3, 0x2e, 0xa9, 0x8b, 0x76, 0xcf, 0xca, 0x70, 0xaa, 0xd5, 0x96, 0x8c, 0x1b, - 0xcb, 0xb5, 0x9c, 0xa5, 0x95, 0xe1, 0x54, 0xab, 0x8a, 0x38, 0xe6, 0xec, 0x51, 0x4c, 0x8d, 0x31, - 0xd2, 0x99, 0x49, 0x8b, 0xf8, 0x7e, 0xa6, 0xc2, 0x79, 0x3b, 0x35, 0x49, 0xf8, 0xb1, 0x27, 0x59, - 0xe8, 0xd1, 0xfd, 0xb6, 0x73, 0x41, 0xe7, 0x5f, 0x5f, 0x22, 0x7b, 0xa9, 0x14, 0xe7, 0x2c, 0x10, - 0x85, 0xd3, 0x94, 0xc7, 0xbe, 0x73, 0x51, 0x5f, 0x0d, 0x63, 0x29, 0xc1, 0xf4, 0xe4, 0xec, 0xf2, - 0xd8, 0xc7, 0x3a, 0x3c, 0x7a, 0x1b, 0x2e, 0xfb, 0xe4, 0x44, 0xb5, 0x03, 0x1a, 0x49, 0x46, 0x85, - 0xb3, 0xae, 0x17, 0xbf, 0xa6, 0xda, 0xf1, 0x5e, 0x5e, 0x81, 0x8b, 0x76, 0xda, 0x91, 0xf1, 0x9c, - 0xe3, 0xa5, 0x9c, 0x63, 0x5e, 0x81, 0x8b, 0x76, 0x2a, 0xd3, 0x11, 0x7d, 0x14, 0xb3, 0x88, 0xba, - 0xce, 0x17, 0xf4, 0x40, 0xa5, 0x33, 0x8d, 0xad, 0x0c, 0xa7, 0x5a, 0xd4, 0x4d, 0x46, 0x4e, 0x47, - 0x1f, 0xc3, 0xfb, 0xe3, 0xed, 0xe4, 0xfb, 0xd1, 0x76, 0x14, 0x91, 0x5e, 0x73, 0x61, 0x78, 0xd8, - 0x44, 0x02, 0xce, 0x10, 0xcf, 0xdb, 0x6f, 0x3b, 0x97, 0x75, 0xee, 0xc7, 0x7d, 0x83, 0xa4, 0x5d, - 0x67, 0x5b, 0x81, 0x60, 0x83, 0xa5, 0x40, 0x03, 0xae, 0x4a, 0x63, 0x63, 0xb2, 0xa0, 0xfb, 0x0a, - 0x04, 0x1b, 0x2c, 0xbd, 0x52, 0xde, 0xdb, 0x6f, 0x3b, 0x5f, 0x9c, 0xf0, 0x4a, 0x15, 0x08, 0x36, - 0x58, 0x88, 0xc1, 0x32, 0x0f, 0xa4, 0x73, 0x65, 0x22, 0xd7, 0xb3, 0xbe, 0x70, 0xee, 0x04, 0x12, - 0x2b, 0x0c, 0xf4, 0x5b, 0x00, 0x61, 0x98, 0x95, 0xe8, 0x55, 0xbd, 0xca, 0x1f, 0x8d, 0x17, 0xb2, - 0x9e, 0xd5, 0xf6, 0x2e, 0x97, 0x51, 0x2f, 0x1b, 0xb2, 0x72, 0x67, 0x20, 0xc7, 0x02, 0xfd, 0x11, - 0xc0, 0x8b, 0xc4, 0x35, 0x23, 0x17, 0xf1, 0x72, 0x27, 0xa8, 0xa2, 0x33, 0x72, 0x6f, 0xdc, 0x65, - 0xde, 0x0c, 0x02, 0xaf, 0xe9, 0x0c, 0xfa, 0xd5, 0x8b, 0xdb, 0x67, 0xa0, 0xe2, 0x33, 0xb9, 0xa0, - 0xbf, 0x00, 0xb8, 0x66, 0xbb, 0x68, 0x8e, 0x61, 0x55, 0x27, 0x90, 0x8e, 0x3b, 0x81, 0xc3, 0x38, - 0x26, 0x8f, 0x97, 0x6d, 0x1e, 0xd7, 0x46, 0xf4, 0x78, 0x94, 0x1a, 0xfa, 0x07, 0x80, 0x4b, 0x2e, - 0x0d, 0x29, 0x77, 0x29, 0x6f, 0x29, 0xae, 0x9b, 0x63, 0x99, 0xa9, 0x87, 0xb9, 0xee, 0xe4, 0x20, - 0x0c, 0xcd, 0xba, 0xa5, 0xb9, 0x94, 0x57, 0x9d, 0xf6, 0xab, 0x97, 0x32, 0xd7, 0xbc, 0x06, 0x17, - 0x58, 0xa2, 0xdf, 0x01, 0xb8, 0x92, 0x6d, 0x80, 0xb9, 0x52, 0xae, 0x4d, 0xb0, 0x0e, 0xf4, 0xf8, - 0xba, 0x5d, 0x04, 0xc4, 0xc3, 0x0c, 0xd0, 0x5f, 0x81, 0x9a, 0xd4, 0x92, 0x29, 0x5d, 0x38, 0x35, - 0x9d, 0xcb, 0xf7, 0xc7, 0x9e, 0xcb, 0x14, 0xc1, 0xa4, 0xf2, 0xb5, 0x6c, 0x14, 0x4c, 0x35, 0xa7, - 0xfd, 0xea, 0x7a, 0x3e, 0x93, 0xa9, 0x02, 0xe7, 0x19, 0xa2, 0x5f, 0x02, 0xb8, 0x44, 0xb3, 0x89, - 0x5b, 0x38, 0x2f, 0x8f, 0x25, 0x89, 0x67, 0x0e, 0xf1, 0xcd, 0x55, 0xb5, 0xdd, 0x39, 0x95, 0xc0, - 0x05, 0x6c, 0x35, 0x41, 0xd2, 0x13, 0xe2, 0x87, 0x1e, 0x75, 0xbe, 0x34, 0xe6, 0x09, 0x72, 0xd7, - 0xc4, 0xc5, 0x09, 0xc0, 0x86, 0x7a, 0xf9, 0x0c, 0x9d, 0x1c, 0xb4, 0x0a, 0xcb, 0xc7, 0xb4, 0x67, - 0x06, 0x7b, 0xac, 0x7e, 0x22, 0x17, 0xce, 0x74, 0x89, 0x17, 0x53, 0xfb, 0x9e, 0x1b, 0x73, 0xd7, - 0xc5, 0x26, 0xf8, 0x37, 0x4b, 0xef, 0x80, 0x8d, 0x27, 0x00, 0x5e, 0x3a, 0xfb, 0x40, 0xbf, 0x50, - 0x5a, 0x7f, 0x00, 0x70, 0x6d, 0xe4, 0xec, 0x9e, 0xc1, 0xe8, 0x51, 0x91, 0xd1, 0xbb, 0xe3, 0x3e, - 0x84, 0x07, 0x32, 0x62, 0xbc, 0xa3, 0x27, 0x8f, 0x3c, 0xbd, 0x5f, 0x03, 0xb8, 0x3a, 0x7c, 0x1c, - 0x5e, 0x64, 0xbe, 0x6a, 0x4f, 0x4a, 0xf0, 0xd2, 0xd9, 0x03, 0x13, 0x8a, 0xd2, 0x97, 0xe1, 0x64, - 0x5e, 0xd8, 0x30, 0x7b, 0x65, 0xa6, 0x8f, 0xca, 0x0f, 0x01, 0x5c, 0x7c, 0x98, 0xda, 0x25, 0x7f, - 0x42, 0x8e, 0xfd, 0x6d, 0x9f, 0xf4, 0x9f, 0x4c, 0x21, 0x70, 0x1e, 0xb7, 0xf6, 0x77, 0x00, 0xd7, - 0xcf, 0x6c, 0xac, 0xea, 0x09, 0x4a, 0x3c, 0x2f, 0x78, 0x2c, 0x74, 0x56, 0xe6, 0xb3, 0x27, 0xe8, - 0xb6, 0x96, 0x62, 0xab, 0xcd, 0x65, 0xaf, 0xf4, 0x79, 0x65, 0xaf, 0xf6, 0x4f, 0x00, 0xaf, 0x7c, - 0x52, 0x25, 0xbe, 0x90, 0x2d, 0xdd, 0x82, 0xf3, 0x76, 0x28, 0xea, 0xe9, 0xed, 0xb4, 0xef, 0x00, - 0xdb, 0x34, 0x7a, 0x38, 0xd5, 0x36, 0xaf, 0x3f, 0x7d, 0x56, 0x99, 0xfa, 0xe8, 0x59, 0x65, 0xea, - 0xe3, 0x67, 0x95, 0xa9, 0x9f, 0x0e, 0x2a, 0xe0, 0xe9, 0xa0, 0x02, 0x3e, 0x1a, 0x54, 0xc0, 0xc7, - 0x83, 0x0a, 0xf8, 0xf7, 0xa0, 0x02, 0x7e, 0xf3, 0x9f, 0xca, 0xd4, 0x0f, 0xe6, 0x2c, 0xf8, 0xff, - 0x03, 0x00, 0x00, 0xff, 0xff, 0x73, 0x96, 0x20, 0xc5, 0xd0, 0x1b, 0x00, 0x00, + 0x19, 0xcf, 0xd8, 0xb9, 0x4e, 0x92, 0x4d, 0x32, 0x6d, 0xca, 0x69, 0x68, 0xed, 0xc4, 0xcb, 0xae, + 0x02, 0x6c, 0x6d, 0xba, 0x17, 0x76, 0x41, 0xe2, 0x21, 0x6e, 0x02, 0xea, 0x92, 0x34, 0xd1, 0xb8, + 0x2d, 0x82, 0xbd, 0x4e, 0x8e, 0xc7, 0xce, 0x34, 0xe7, 0xd6, 0x33, 0x73, 0xdc, 0x44, 0x02, 0xc4, + 0x45, 0x2b, 0x24, 0x24, 0x2e, 0x82, 0xbe, 0x20, 0xf1, 0x02, 0x8f, 0x08, 0xc1, 0x03, 0x3c, 0xf2, + 0xc8, 0x43, 0x1f, 0x57, 0xe2, 0x65, 0x9f, 0x2c, 0x6a, 0xfe, 0x05, 0x10, 0x52, 0x9e, 0x56, 0x73, + 0x39, 0x37, 0x3b, 0xde, 0xad, 0xb4, 0x76, 0xfb, 0xe6, 0xf3, 0xdd, 0x7e, 0xbf, 0xf9, 0xe6, 0x9b, + 0x6f, 0xbe, 0x31, 0x6c, 0x1d, 0xbf, 0xc1, 0xab, 0xcc, 0xaf, 0x1d, 0x47, 0x87, 0x34, 0xf4, 0xa8, + 0xa0, 0xbc, 0xd6, 0xa1, 0x5e, 0xd3, 0x0f, 0x6b, 0x46, 0x41, 0x02, 0x46, 0x4f, 0x04, 0xf5, 0x38, + 0xf3, 0x3d, 0x7e, 0x8d, 0x04, 0x8c, 0xd3, 0xb0, 0x43, 0xc3, 0x5a, 0x70, 0xdc, 0x96, 0x3a, 0x9e, + 0x37, 0xa8, 0x75, 0xae, 0x1f, 0x52, 0x41, 0xae, 0xd7, 0xda, 0xd4, 0xa3, 0x21, 0x11, 0xb4, 0x59, + 0x0d, 0x42, 0x5f, 0xf8, 0xe8, 0x1b, 0x3a, 0x5c, 0x35, 0x67, 0xfd, 0x5e, 0x12, 0xae, 0x1a, 0x1c, + 0xb7, 0xa5, 0x8e, 0xe7, 0x0d, 0xaa, 0x26, 0xdc, 0xda, 0xb5, 0x36, 0x13, 0x47, 0xd1, 0x61, 0xd5, + 0xf6, 0xdd, 0x5a, 0xdb, 0x6f, 0xfb, 0x35, 0x15, 0xf5, 0x30, 0x6a, 0xa9, 0x2f, 0xf5, 0xa1, 0x7e, + 0x69, 0xb4, 0xb5, 0x57, 0x53, 0xf2, 0x2e, 0xb1, 0x8f, 0x98, 0x47, 0xc3, 0xd3, 0x94, 0xb1, 0x4b, + 0x05, 0xa9, 0x75, 0x06, 0x38, 0xae, 0xd5, 0x86, 0x79, 0x85, 0x91, 0x27, 0x98, 0x4b, 0x07, 0x1c, + 0xbe, 0xfa, 0x69, 0x0e, 0xdc, 0x3e, 0xa2, 0x2e, 0x19, 0xf0, 0x7b, 0x65, 0x98, 0x5f, 0x24, 0x98, + 0x53, 0x63, 0x9e, 0xe0, 0x22, 0xec, 0x77, 0xaa, 0xfc, 0xb4, 0x08, 0xad, 0x1b, 0x11, 0x17, 0xbe, + 0x8b, 0x29, 0xf7, 0xa3, 0xd0, 0xa6, 0xdb, 0xb4, 0xc5, 0x3c, 0x26, 0x98, 0xef, 0xa1, 0xf7, 0xe1, + 0xac, 0x5c, 0x55, 0x93, 0x08, 0x62, 0x81, 0x75, 0xb0, 0x39, 0xff, 0xf2, 0x57, 0xaa, 0x69, 0xc6, + 0x13, 0x90, 0x34, 0xcd, 0xd2, 0xba, 0xda, 0xb9, 0x5e, 0xdd, 0x3f, 0xbc, 0x47, 0x6d, 0xb1, 0x47, + 0x05, 0xa9, 0xa3, 0x47, 0xdd, 0xf2, 0x44, 0xaf, 0x5b, 0x86, 0xa9, 0x0c, 0x27, 0x51, 0xd1, 0x0f, + 0xe0, 0x24, 0x0f, 0xa8, 0x6d, 0x15, 0x54, 0xf4, 0xb7, 0xaa, 0x9f, 0x69, 0x3f, 0xab, 0xc3, 0x16, + 0xd2, 0x08, 0xa8, 0x5d, 0x5f, 0x30, 0x44, 0x26, 0xe5, 0x17, 0x56, 0xb0, 0xe8, 0x03, 0x00, 0xa7, + 0xb9, 0x20, 0x22, 0xe2, 0x56, 0x51, 0x31, 0x78, 0x67, 0x5c, 0x0c, 0x14, 0x48, 0xfd, 0x39, 0xc3, + 0x61, 0x5a, 0x7f, 0x63, 0x03, 0x5e, 0xf9, 0x6f, 0x01, 0x6e, 0x0c, 0x73, 0xbd, 0xe1, 0x7b, 0x4d, + 0xbd, 0x1d, 0x37, 0xe1, 0xa4, 0x38, 0x0d, 0xa8, 0xda, 0x8a, 0xb9, 0xfa, 0x6b, 0xf1, 0x7a, 0x6e, + 0x9f, 0x06, 0xf4, 0xac, 0x5b, 0x7e, 0xe1, 0x53, 0x03, 0x48, 0x43, 0xac, 0x42, 0xa0, 0xaf, 0x25, + 0xeb, 0x2e, 0xa8, 0x60, 0x1b, 0x79, 0x62, 0x67, 0xdd, 0xf2, 0x52, 0xe2, 0x96, 0xe7, 0x8a, 0x3a, + 0x10, 0x39, 0x84, 0x8b, 0xdb, 0x21, 0xf1, 0xb8, 0x0e, 0xcb, 0x5c, 0x6a, 0xd2, 0xf7, 0xa5, 0x27, + 0x2b, 0x0f, 0xe9, 0x51, 0x5f, 0x33, 0x90, 0x68, 0x77, 0x20, 0x1a, 0x3e, 0x07, 0x01, 0xbd, 0x08, + 0xa7, 0x43, 0x4a, 0xb8, 0xef, 0x59, 0x93, 0x8a, 0x72, 0x92, 0x4b, 0xac, 0xa4, 0xd8, 0x68, 0xd1, + 0x17, 0xe1, 0x8c, 0x4b, 0x39, 0x27, 0x6d, 0x6a, 0x4d, 0x29, 0xc3, 0x25, 0x63, 0x38, 0xb3, 0xa7, + 0xc5, 0x38, 0xd6, 0x57, 0xce, 0x00, 0xbc, 0x32, 0x2c, 0x6b, 0xbb, 0x8c, 0x0b, 0xf4, 0xf6, 0xc0, + 0x01, 0xa8, 0x3e, 0xd9, 0x0a, 0xa5, 0xb7, 0x2a, 0xff, 0x65, 0x03, 0x3e, 0x1b, 0x4b, 0x32, 0xc5, + 0xff, 0x7d, 0x38, 0xc5, 0x04, 0x75, 0xe5, 0x1e, 0x14, 0x37, 0xe7, 0x5f, 0xfe, 0xce, 0x98, 0x6a, + 0xaf, 0xbe, 0x68, 0x38, 0x4c, 0xdd, 0x94, 0x68, 0x58, 0x83, 0x56, 0xfe, 0x07, 0xe0, 0xd5, 0x61, + 0x2e, 0xb7, 0x88, 0x4b, 0xb9, 0xcc, 0x78, 0xe0, 0x44, 0x21, 0x71, 0x4c, 0xc5, 0x25, 0x19, 0x3f, + 0x50, 0x52, 0x6c, 0xb4, 0xe8, 0x25, 0x38, 0xcb, 0x99, 0xd7, 0x8e, 0x1c, 0x12, 0x9a, 0x72, 0x4a, + 0x56, 0xdd, 0x30, 0x72, 0x9c, 0x58, 0xa0, 0x2a, 0x84, 0xfc, 0xc8, 0x0f, 0x85, 0xc2, 0xb0, 0x8a, + 0xeb, 0x45, 0x19, 0x59, 0x36, 0x88, 0x46, 0x22, 0xc5, 0x19, 0x0b, 0xb4, 0x0e, 0x27, 0x8f, 0x99, + 0xd7, 0x34, 0xbb, 0x9e, 0x9c, 0xe2, 0x6f, 0x33, 0xaf, 0x89, 0x95, 0x46, 0xe2, 0x3b, 0x8c, 0x0b, + 0x29, 0x31, 0x5b, 0x9e, 0xcb, 0xba, 0xb2, 0x4c, 0x2c, 0x2a, 0xff, 0x9c, 0x1c, 0xbe, 0xe9, 0xb2, + 0x35, 0xa0, 0xe7, 0xe1, 0x54, 0x3b, 0xf4, 0xa3, 0xc0, 0xac, 0x3a, 0xc9, 0xde, 0xb7, 0xa4, 0x10, + 0x6b, 0x9d, 0xac, 0xb2, 0x0e, 0x0d, 0xe5, 0x06, 0x98, 0x25, 0x27, 0x55, 0x76, 0x57, 0x8b, 0x71, + 0xac, 0x47, 0x3f, 0x06, 0x70, 0xca, 0x33, 0x8b, 0x95, 0x25, 0xf4, 0xf6, 0x98, 0xf6, 0x59, 0xa5, + 0x2b, 0xa5, 0xab, 0x33, 0xa9, 0x91, 0xd1, 0xab, 0x70, 0x8a, 0xdb, 0x7e, 0x40, 0x4d, 0x16, 0x4b, + 0xb1, 0x51, 0x43, 0x0a, 0xcf, 0xba, 0xe5, 0xc5, 0x38, 0x9c, 0x12, 0x60, 0x6d, 0x8c, 0x7e, 0x06, + 0x20, 0xec, 0x10, 0x87, 0x35, 0x89, 0x8c, 0xaf, 0x72, 0x3b, 0xea, 0x32, 0xbd, 0x9b, 0x84, 0xd7, + 0x45, 0x90, 0x7e, 0xe3, 0x0c, 0x34, 0xfa, 0x15, 0x80, 0x0b, 0x3c, 0x3a, 0x0c, 0x8d, 0x17, 0xb7, + 0xa6, 0x15, 0x97, 0xef, 0x8e, 0x94, 0x4b, 0x23, 0x03, 0x50, 0x5f, 0xee, 0x75, 0xcb, 0x0b, 0x59, + 0x09, 0xce, 0x11, 0xa8, 0xfc, 0xab, 0x00, 0x4b, 0x9f, 0xdc, 0xed, 0xd1, 0x43, 0x00, 0xa1, 0x1d, + 0x77, 0x51, 0x6e, 0x01, 0x75, 0xca, 0xdf, 0x1f, 0xd3, 0xee, 0x27, 0xed, 0x3a, 0xbd, 0x71, 0x13, + 0x11, 0xc7, 0x19, 0x1e, 0xe8, 0x77, 0x00, 0x2e, 0x12, 0xdb, 0xa6, 0x81, 0xa0, 0x4d, 0x7d, 0x08, + 0x0b, 0x4f, 0xa1, 0x2e, 0x57, 0x0d, 0xab, 0xc5, 0xad, 0x2c, 0x34, 0xce, 0x33, 0xa9, 0xfc, 0x1f, + 0xf4, 0x67, 0x35, 0xb3, 0x05, 0x0d, 0x9b, 0x38, 0x14, 0x6d, 0xc3, 0x65, 0x79, 0x77, 0x63, 0x1a, + 0x38, 0xcc, 0x26, 0xfc, 0x80, 0x88, 0x23, 0x73, 0x52, 0x2d, 0x03, 0xb1, 0xdc, 0xe8, 0xd3, 0xe3, + 0x01, 0x0f, 0xf4, 0x26, 0x44, 0xfa, 0x3e, 0xcb, 0xc5, 0xd1, 0x47, 0x39, 0xb9, 0x99, 0x1a, 0x03, + 0x16, 0xf8, 0x1c, 0x2f, 0x74, 0x03, 0xae, 0x38, 0xe4, 0x90, 0x3a, 0x0d, 0xea, 0x50, 0x5b, 0xf8, + 0xa1, 0x0a, 0x55, 0x54, 0xa1, 0x56, 0x7b, 0xdd, 0xf2, 0xca, 0x6e, 0xbf, 0x12, 0x0f, 0xda, 0x57, + 0x36, 0x60, 0x79, 0xf8, 0xc2, 0xf5, 0x94, 0xf0, 0x87, 0x02, 0x5c, 0x1b, 0x5e, 0xb1, 0xe8, 0x27, + 0xe9, 0x30, 0xa3, 0xef, 0xaa, 0x77, 0xc7, 0x75, 0x3a, 0xcc, 0x34, 0x03, 0x07, 0x27, 0x19, 0xf4, + 0x43, 0xd9, 0x68, 0x88, 0x43, 0x4d, 0x4d, 0xbd, 0x33, 0x36, 0x0a, 0x12, 0xa4, 0x3e, 0xa7, 0x7b, + 0x18, 0x71, 0x54, 0xcb, 0x22, 0x0e, 0xad, 0xfc, 0x09, 0xf4, 0xcf, 0xb3, 0x69, 0x47, 0x41, 0xbf, + 0x00, 0x70, 0xc9, 0x0f, 0xa8, 0xb7, 0x75, 0x70, 0xf3, 0xee, 0x2b, 0x0d, 0x35, 0x45, 0x9b, 0x54, + 0xdd, 0xfa, 0x8c, 0x3c, 0xdf, 0x6c, 0xec, 0xdf, 0xd2, 0x01, 0x0f, 0x42, 0x3f, 0xe0, 0xf5, 0x0b, + 0xbd, 0x6e, 0x79, 0x69, 0x3f, 0x0f, 0x85, 0xfb, 0xb1, 0x2b, 0x2e, 0x5c, 0xdd, 0x39, 0x11, 0x34, + 0xf4, 0x88, 0xb3, 0xed, 0xdb, 0x91, 0x4b, 0x3d, 0xa1, 0x89, 0xbe, 0x06, 0xe7, 0x9b, 0x94, 0xdb, + 0x21, 0x0b, 0x54, 0xe3, 0xd5, 0xe5, 0x7d, 0xc1, 0x94, 0xe5, 0xfc, 0x76, 0xaa, 0xc2, 0x59, 0x3b, + 0x74, 0x15, 0x16, 0xa3, 0xd0, 0x31, 0x55, 0x3c, 0x6f, 0xcc, 0x8b, 0x77, 0xf0, 0x2e, 0x96, 0xf2, + 0xca, 0x06, 0x9c, 0x94, 0x3c, 0xd1, 0x65, 0x58, 0x0c, 0xc9, 0x03, 0x15, 0x75, 0xa1, 0x3e, 0x23, + 0x4d, 0x30, 0x79, 0x80, 0xa5, 0xac, 0xf2, 0xe7, 0x2b, 0x70, 0xa9, 0x6f, 0x2d, 0x68, 0x0d, 0x16, + 0x58, 0xd3, 0x70, 0x80, 0x26, 0x68, 0xe1, 0xe6, 0x36, 0x2e, 0xb0, 0x26, 0x7a, 0x1d, 0x4e, 0xeb, + 0xd7, 0x88, 0x01, 0x2d, 0x27, 0x73, 0xa4, 0x92, 0xca, 0x9b, 0x25, 0x0d, 0x27, 0x89, 0x18, 0x73, + 0xc5, 0x81, 0xb6, 0xcc, 0x29, 0xd1, 0x1c, 0x68, 0x0b, 0x4b, 0x59, 0xff, 0xe2, 0x27, 0x9f, 0x70, + 0xf1, 0xeb, 0x66, 0x3a, 0x9e, 0xca, 0xcf, 0x09, 0x99, 0xa1, 0xf7, 0x45, 0x38, 0xdd, 0xf2, 0x43, + 0x97, 0x08, 0x75, 0x7b, 0x64, 0xe6, 0x99, 0x6f, 0x2a, 0x29, 0x36, 0x5a, 0x39, 0x00, 0x08, 0x26, + 0x1c, 0x6a, 0xcd, 0xe4, 0x07, 0x80, 0xdb, 0x52, 0x88, 0xb5, 0x0e, 0xdd, 0x83, 0x33, 0x4d, 0xda, + 0x22, 0x91, 0x23, 0xac, 0x59, 0x55, 0x42, 0x37, 0x46, 0x50, 0x42, 0xf5, 0x79, 0x39, 0x41, 0x6c, + 0xeb, 0xb8, 0x38, 0x06, 0x40, 0x2f, 0xc0, 0x19, 0x97, 0x9c, 0x30, 0x37, 0x72, 0xad, 0xb9, 0x75, + 0xb0, 0x09, 0xb4, 0xd9, 0x9e, 0x16, 0xe1, 0x58, 0x27, 0x3b, 0x23, 0x3d, 0xb1, 0x9d, 0x88, 0xb3, + 0x0e, 0x35, 0x4a, 0x0b, 0xae, 0x83, 0xcd, 0xd9, 0xb4, 0x33, 0xee, 0xf4, 0xe9, 0xf1, 0x80, 0x87, + 0x02, 0x63, 0x9e, 0x72, 0x9e, 0xcf, 0x80, 0x69, 0x11, 0x8e, 0x75, 0x79, 0x30, 0x63, 0xbf, 0x30, + 0x0c, 0xcc, 0x38, 0x0f, 0x78, 0xa0, 0x2f, 0xc3, 0x39, 0x97, 0x9c, 0xec, 0x52, 0xaf, 0x2d, 0x8e, + 0xac, 0xc5, 0x75, 0xb0, 0x59, 0xac, 0x2f, 0xf6, 0xba, 0xe5, 0xb9, 0xbd, 0x58, 0x88, 0x53, 0xbd, + 0x32, 0x66, 0x9e, 0x31, 0x7e, 0x2e, 0x63, 0x1c, 0x0b, 0x71, 0xaa, 0x97, 0x03, 0x5a, 0x40, 0x84, + 0x3c, 0x5c, 0xd6, 0x52, 0x7e, 0x40, 0x3b, 0xd0, 0x62, 0x1c, 0xeb, 0xd1, 0x26, 0x9c, 0x75, 0xc9, + 0x89, 0x1a, 0x8e, 0xad, 0x65, 0x15, 0x76, 0x41, 0xce, 0x8e, 0x7b, 0x46, 0x86, 0x13, 0xad, 0xb2, + 0x64, 0x9e, 0xb6, 0x5c, 0xc9, 0x58, 0x1a, 0x19, 0x4e, 0xb4, 0xb2, 0x88, 0x23, 0x8f, 0xdd, 0x8f, + 0xa8, 0x36, 0x46, 0x2a, 0x33, 0x49, 0x11, 0xdf, 0x49, 0x55, 0x38, 0x6b, 0x27, 0x87, 0x63, 0x37, + 0x72, 0x04, 0x0b, 0x1c, 0xba, 0xdf, 0xb2, 0x2e, 0xa8, 0xfc, 0xab, 0xb9, 0x68, 0x2f, 0x91, 0xe2, + 0x8c, 0x05, 0xa2, 0x70, 0x92, 0x7a, 0x91, 0x6b, 0x5d, 0x54, 0xb3, 0xc5, 0x48, 0x4a, 0x30, 0x39, + 0x39, 0x3b, 0x5e, 0xe4, 0x62, 0x15, 0x1e, 0xbd, 0x0e, 0x17, 0x5d, 0x72, 0x22, 0xdb, 0x01, 0x0d, + 0x05, 0xa3, 0xdc, 0x5a, 0x55, 0x8b, 0x5f, 0x91, 0xf7, 0xf9, 0x5e, 0x56, 0x81, 0xf3, 0x76, 0xca, + 0x91, 0x79, 0x19, 0xc7, 0x4b, 0x19, 0xc7, 0xac, 0x02, 0xe7, 0xed, 0x64, 0xa6, 0x43, 0x7a, 0x3f, + 0x62, 0x21, 0x6d, 0x5a, 0x9f, 0x53, 0x6f, 0x04, 0x95, 0x69, 0x6c, 0x64, 0x38, 0xd1, 0xa2, 0x4e, + 0xfc, 0x8a, 0xb2, 0xd4, 0x31, 0xbc, 0x33, 0xda, 0x4e, 0xbe, 0x1f, 0x6e, 0x85, 0x21, 0x39, 0xd5, + 0x37, 0x4d, 0xf6, 0xfd, 0x84, 0x38, 0x9c, 0x22, 0x8e, 0xb3, 0xdf, 0xb2, 0x2e, 0xab, 0xdc, 0x8f, + 0xfa, 0x06, 0x49, 0xba, 0xce, 0x96, 0x04, 0xc1, 0x1a, 0x4b, 0x82, 0xfa, 0x9e, 0x2c, 0x8d, 0xb5, + 0xf1, 0x82, 0xee, 0x4b, 0x10, 0xac, 0xb1, 0xd4, 0x4a, 0xbd, 0xd3, 0xfd, 0x96, 0xf5, 0xf9, 0x31, + 0xaf, 0x54, 0x82, 0x60, 0x8d, 0x85, 0x18, 0x2c, 0x7a, 0xbe, 0xb0, 0xae, 0x8c, 0xe5, 0x7a, 0x56, + 0x17, 0xce, 0x2d, 0x5f, 0x60, 0x89, 0x81, 0x7e, 0x03, 0x20, 0x0c, 0xd2, 0x12, 0xbd, 0xaa, 0x56, + 0xf9, 0xee, 0x68, 0x21, 0xab, 0x69, 0x6d, 0xef, 0x78, 0x22, 0x3c, 0x4d, 0xa7, 0xf4, 0xcc, 0x19, + 0xc8, 0xb0, 0x40, 0x7f, 0x04, 0xf0, 0x22, 0x69, 0xea, 0x99, 0x9d, 0x38, 0x99, 0x13, 0x54, 0x52, + 0x19, 0xb9, 0x3d, 0xea, 0x32, 0xaf, 0xfb, 0xbe, 0x53, 0xb7, 0x7a, 0xdd, 0xf2, 0xc5, 0xad, 0x73, + 0x50, 0xf1, 0xb9, 0x5c, 0xd0, 0x5f, 0x00, 0x5c, 0x31, 0x5d, 0x34, 0xc3, 0xb0, 0xac, 0x12, 0x48, + 0x47, 0x9d, 0xc0, 0x7e, 0x1c, 0x9d, 0xc7, 0xcb, 0x26, 0x8f, 0x2b, 0x03, 0x7a, 0x3c, 0x48, 0x0d, + 0xfd, 0x1d, 0xc0, 0x85, 0x26, 0x0d, 0xa8, 0xd7, 0xa4, 0x9e, 0x2d, 0xb9, 0xae, 0x8f, 0xe4, 0x51, + 0xd6, 0xcf, 0x75, 0x3b, 0x03, 0xa1, 0x69, 0x56, 0x0d, 0xcd, 0x85, 0xac, 0xea, 0xac, 0x5b, 0xbe, + 0x94, 0xba, 0x66, 0x35, 0x38, 0xc7, 0x12, 0xfd, 0x16, 0xc0, 0xa5, 0x74, 0x03, 0xf4, 0x95, 0xb2, + 0x31, 0xc6, 0x3a, 0x50, 0xe3, 0xeb, 0x56, 0x1e, 0x10, 0xf7, 0x33, 0x40, 0x7f, 0x05, 0x72, 0x52, + 0x8b, 0x9f, 0x79, 0xdc, 0xaa, 0xa8, 0x5c, 0xbe, 0x37, 0xf2, 0x5c, 0x26, 0x08, 0x3a, 0x95, 0x2f, + 0xa5, 0xa3, 0x60, 0xa2, 0x39, 0xeb, 0x96, 0x57, 0xb3, 0x99, 0x4c, 0x14, 0x38, 0xcb, 0x10, 0xfd, + 0x1c, 0xc0, 0x05, 0x9a, 0x4e, 0xdc, 0xdc, 0x7a, 0x7e, 0x24, 0x49, 0x3c, 0x77, 0x88, 0xd7, 0xff, + 0x20, 0x64, 0x54, 0x1c, 0xe7, 0xb0, 0xe5, 0x04, 0x49, 0x4f, 0x88, 0x1b, 0x38, 0xd4, 0xfa, 0xc2, + 0x88, 0x27, 0xc8, 0x1d, 0x1d, 0x17, 0xc7, 0x00, 0x6b, 0xf2, 0xe5, 0xd3, 0x77, 0x72, 0xd0, 0x32, + 0x2c, 0x1e, 0xd3, 0x53, 0x3d, 0xd8, 0x63, 0xf9, 0x13, 0x35, 0xe1, 0x54, 0x87, 0x38, 0x51, 0xfc, + 0x78, 0x1b, 0x71, 0xd7, 0xc5, 0x3a, 0xf8, 0xd7, 0x0b, 0x6f, 0x80, 0xb5, 0x87, 0x00, 0x5e, 0x3a, + 0xff, 0x40, 0x3f, 0x53, 0x5a, 0xbf, 0x07, 0x70, 0x65, 0xe0, 0xec, 0x9e, 0xc3, 0xe8, 0x7e, 0x9e, + 0xd1, 0x5b, 0xa3, 0x3e, 0x84, 0x0d, 0x11, 0x32, 0xaf, 0xad, 0x26, 0x8f, 0x2c, 0xbd, 0x5f, 0x02, + 0xb8, 0xdc, 0x7f, 0x1c, 0x9e, 0x65, 0xbe, 0x2a, 0x0f, 0x0b, 0xf0, 0xd2, 0xf9, 0x03, 0x13, 0x0a, + 0x93, 0x97, 0xe1, 0x78, 0x5e, 0xd8, 0x30, 0x7d, 0x65, 0x26, 0x8f, 0xca, 0x0f, 0x00, 0x9c, 0xbf, + 0x97, 0xd8, 0xc5, 0xff, 0xab, 0x8f, 0xfc, 0x6d, 0x1f, 0xf7, 0x9f, 0x54, 0xc1, 0x71, 0x16, 0xb7, + 0xf2, 0x37, 0x00, 0x57, 0xcf, 0x6d, 0xac, 0xf2, 0x09, 0x4a, 0x1c, 0xc7, 0x7f, 0xa0, 0xff, 0xa2, + 0x99, 0x4d, 0x9f, 0xa0, 0x5b, 0x4a, 0x8a, 0x8d, 0x36, 0x93, 0xbd, 0xc2, 0xd3, 0xca, 0x5e, 0xe5, + 0x1f, 0x00, 0x5e, 0xf9, 0xa4, 0x4a, 0x7c, 0x26, 0x5b, 0xba, 0x09, 0x67, 0xcd, 0x50, 0x74, 0xaa, + 0xb6, 0xd3, 0xbc, 0x03, 0x4c, 0xd3, 0x38, 0xc5, 0x89, 0xb6, 0x7e, 0xed, 0xd1, 0xe3, 0xd2, 0xc4, + 0x87, 0x8f, 0x4b, 0x13, 0x1f, 0x3d, 0x2e, 0x4d, 0xfc, 0xa8, 0x57, 0x02, 0x8f, 0x7a, 0x25, 0xf0, + 0x61, 0xaf, 0x04, 0x3e, 0xea, 0x95, 0xc0, 0xbf, 0x7b, 0x25, 0xf0, 0xeb, 0xff, 0x94, 0x26, 0xbe, + 0x37, 0x63, 0xc0, 0x3f, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x35, 0x69, 0x71, 0xa6, 0xa3, 0x1e, 0x00, + 0x00, } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.proto b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.proto index c4d2b2dde31..ffc94d07c93 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.proto +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.proto @@ -107,6 +107,12 @@ message CustomResourceDefinitionSpec { // Validation describes the validation methods for CustomResources // +optional optional CustomResourceValidation validation = 5; + + // Subresources describes the subresources for CustomResources + // This field is alpha-level and should only be sent to servers that enable + // subresources via the CustomResourceSubresources feature gate. + // +optional + optional CustomResourceSubresources subresources = 6; } // CustomResourceDefinitionStatus indicates the state of the CustomResourceDefinition @@ -119,6 +125,48 @@ message CustomResourceDefinitionStatus { optional CustomResourceDefinitionNames acceptedNames = 2; } +// CustomResourceSubresourceScale defines how to serve the scale subresource for CustomResources. +message CustomResourceSubresourceScale { + // SpecReplicasPath defines the JSON path inside of a CustomResource that corresponds to Scale.Spec.Replicas. + // Only JSON paths without the array notation are allowed. + // Must be a JSON Path under .spec. + // If there is no value under the given path in the CustomResource, the /scale subresource will return an error on GET. + optional string specReplicasPath = 1; + + // StatusReplicasPath defines the JSON path inside of a CustomResource that corresponds to Scale.Status.Replicas. + // Only JSON paths without the array notation are allowed. + // Must be a JSON Path under .status. + // If there is no value under the given path in the CustomResource, the status replica value in the /scale subresource + // will default to 0. + optional string statusReplicasPath = 2; + + // LabelSelectorPath defines the JSON path inside of a CustomResource that corresponds to Scale.Status.Selector. + // Only JSON paths without the array notation are allowed. + // Must be a JSON Path under .status. + // Must be set to work with HPA. + // If there is no value under the given path in the CustomResource, the status label selector value in the /scale + // subresource will default to the empty string. + // +optional + optional string labelSelectorPath = 3; +} + +// CustomResourceSubresourceStatus defines how to serve the status subresource for CustomResources. +// Status is represented by the `.status` JSON path inside of a CustomResource. When set, +// * exposes a /status subresource for the custom resource +// * PUT requests to the /status subresource take a custom resource object, and ignore changes to anything except the status stanza +// * PUT/POST/PATCH requests to the custom resource ignore changes to the status stanza +message CustomResourceSubresourceStatus { +} + +// CustomResourceSubresources defines the status and scale subresources for CustomResources. +message CustomResourceSubresources { + // Status denotes the status subresource for CustomResources + optional CustomResourceSubresourceStatus status = 1; + + // Scale denotes the scale subresource for CustomResources + optional CustomResourceSubresourceScale scale = 2; +} + // CustomResourceValidation is a list of validation methods for CustomResources. message CustomResourceValidation { // OpenAPIV3Schema is the OpenAPI v3 schema to be validated against. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/zz_generated.conversion.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/zz_generated.conversion.go index f7d46f6e502..84996455f77 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/zz_generated.conversion.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/zz_generated.conversion.go @@ -48,6 +48,12 @@ func RegisterConversions(scheme *runtime.Scheme) error { Convert_apiextensions_CustomResourceDefinitionSpec_To_v1beta1_CustomResourceDefinitionSpec, Convert_v1beta1_CustomResourceDefinitionStatus_To_apiextensions_CustomResourceDefinitionStatus, Convert_apiextensions_CustomResourceDefinitionStatus_To_v1beta1_CustomResourceDefinitionStatus, + Convert_v1beta1_CustomResourceSubresourceScale_To_apiextensions_CustomResourceSubresourceScale, + Convert_apiextensions_CustomResourceSubresourceScale_To_v1beta1_CustomResourceSubresourceScale, + Convert_v1beta1_CustomResourceSubresourceStatus_To_apiextensions_CustomResourceSubresourceStatus, + Convert_apiextensions_CustomResourceSubresourceStatus_To_v1beta1_CustomResourceSubresourceStatus, + Convert_v1beta1_CustomResourceSubresources_To_apiextensions_CustomResourceSubresources, + Convert_apiextensions_CustomResourceSubresources_To_v1beta1_CustomResourceSubresources, Convert_v1beta1_CustomResourceValidation_To_apiextensions_CustomResourceValidation, Convert_apiextensions_CustomResourceValidation_To_v1beta1_CustomResourceValidation, Convert_v1beta1_ExternalDocumentation_To_apiextensions_ExternalDocumentation, @@ -211,6 +217,7 @@ func autoConvert_v1beta1_CustomResourceDefinitionSpec_To_apiextensions_CustomRes } else { out.Validation = nil } + out.Subresources = (*apiextensions.CustomResourceSubresources)(unsafe.Pointer(in.Subresources)) return nil } @@ -235,6 +242,7 @@ func autoConvert_apiextensions_CustomResourceDefinitionSpec_To_v1beta1_CustomRes } else { out.Validation = nil } + out.Subresources = (*CustomResourceSubresources)(unsafe.Pointer(in.Subresources)) return nil } @@ -269,6 +277,70 @@ func Convert_apiextensions_CustomResourceDefinitionStatus_To_v1beta1_CustomResou return autoConvert_apiextensions_CustomResourceDefinitionStatus_To_v1beta1_CustomResourceDefinitionStatus(in, out, s) } +func autoConvert_v1beta1_CustomResourceSubresourceScale_To_apiextensions_CustomResourceSubresourceScale(in *CustomResourceSubresourceScale, out *apiextensions.CustomResourceSubresourceScale, s conversion.Scope) error { + out.SpecReplicasPath = in.SpecReplicasPath + out.StatusReplicasPath = in.StatusReplicasPath + out.LabelSelectorPath = (*string)(unsafe.Pointer(in.LabelSelectorPath)) + return nil +} + +// Convert_v1beta1_CustomResourceSubresourceScale_To_apiextensions_CustomResourceSubresourceScale is an autogenerated conversion function. +func Convert_v1beta1_CustomResourceSubresourceScale_To_apiextensions_CustomResourceSubresourceScale(in *CustomResourceSubresourceScale, out *apiextensions.CustomResourceSubresourceScale, s conversion.Scope) error { + return autoConvert_v1beta1_CustomResourceSubresourceScale_To_apiextensions_CustomResourceSubresourceScale(in, out, s) +} + +func autoConvert_apiextensions_CustomResourceSubresourceScale_To_v1beta1_CustomResourceSubresourceScale(in *apiextensions.CustomResourceSubresourceScale, out *CustomResourceSubresourceScale, s conversion.Scope) error { + out.SpecReplicasPath = in.SpecReplicasPath + out.StatusReplicasPath = in.StatusReplicasPath + out.LabelSelectorPath = (*string)(unsafe.Pointer(in.LabelSelectorPath)) + return nil +} + +// Convert_apiextensions_CustomResourceSubresourceScale_To_v1beta1_CustomResourceSubresourceScale is an autogenerated conversion function. +func Convert_apiextensions_CustomResourceSubresourceScale_To_v1beta1_CustomResourceSubresourceScale(in *apiextensions.CustomResourceSubresourceScale, out *CustomResourceSubresourceScale, s conversion.Scope) error { + return autoConvert_apiextensions_CustomResourceSubresourceScale_To_v1beta1_CustomResourceSubresourceScale(in, out, s) +} + +func autoConvert_v1beta1_CustomResourceSubresourceStatus_To_apiextensions_CustomResourceSubresourceStatus(in *CustomResourceSubresourceStatus, out *apiextensions.CustomResourceSubresourceStatus, s conversion.Scope) error { + return nil +} + +// Convert_v1beta1_CustomResourceSubresourceStatus_To_apiextensions_CustomResourceSubresourceStatus is an autogenerated conversion function. +func Convert_v1beta1_CustomResourceSubresourceStatus_To_apiextensions_CustomResourceSubresourceStatus(in *CustomResourceSubresourceStatus, out *apiextensions.CustomResourceSubresourceStatus, s conversion.Scope) error { + return autoConvert_v1beta1_CustomResourceSubresourceStatus_To_apiextensions_CustomResourceSubresourceStatus(in, out, s) +} + +func autoConvert_apiextensions_CustomResourceSubresourceStatus_To_v1beta1_CustomResourceSubresourceStatus(in *apiextensions.CustomResourceSubresourceStatus, out *CustomResourceSubresourceStatus, s conversion.Scope) error { + return nil +} + +// Convert_apiextensions_CustomResourceSubresourceStatus_To_v1beta1_CustomResourceSubresourceStatus is an autogenerated conversion function. +func Convert_apiextensions_CustomResourceSubresourceStatus_To_v1beta1_CustomResourceSubresourceStatus(in *apiextensions.CustomResourceSubresourceStatus, out *CustomResourceSubresourceStatus, s conversion.Scope) error { + return autoConvert_apiextensions_CustomResourceSubresourceStatus_To_v1beta1_CustomResourceSubresourceStatus(in, out, s) +} + +func autoConvert_v1beta1_CustomResourceSubresources_To_apiextensions_CustomResourceSubresources(in *CustomResourceSubresources, out *apiextensions.CustomResourceSubresources, s conversion.Scope) error { + out.Status = (*apiextensions.CustomResourceSubresourceStatus)(unsafe.Pointer(in.Status)) + out.Scale = (*apiextensions.CustomResourceSubresourceScale)(unsafe.Pointer(in.Scale)) + return nil +} + +// Convert_v1beta1_CustomResourceSubresources_To_apiextensions_CustomResourceSubresources is an autogenerated conversion function. +func Convert_v1beta1_CustomResourceSubresources_To_apiextensions_CustomResourceSubresources(in *CustomResourceSubresources, out *apiextensions.CustomResourceSubresources, s conversion.Scope) error { + return autoConvert_v1beta1_CustomResourceSubresources_To_apiextensions_CustomResourceSubresources(in, out, s) +} + +func autoConvert_apiextensions_CustomResourceSubresources_To_v1beta1_CustomResourceSubresources(in *apiextensions.CustomResourceSubresources, out *CustomResourceSubresources, s conversion.Scope) error { + out.Status = (*CustomResourceSubresourceStatus)(unsafe.Pointer(in.Status)) + out.Scale = (*CustomResourceSubresourceScale)(unsafe.Pointer(in.Scale)) + return nil +} + +// Convert_apiextensions_CustomResourceSubresources_To_v1beta1_CustomResourceSubresources is an autogenerated conversion function. +func Convert_apiextensions_CustomResourceSubresources_To_v1beta1_CustomResourceSubresources(in *apiextensions.CustomResourceSubresources, out *CustomResourceSubresources, s conversion.Scope) error { + return autoConvert_apiextensions_CustomResourceSubresources_To_v1beta1_CustomResourceSubresources(in, out, s) +} + func autoConvert_v1beta1_CustomResourceValidation_To_apiextensions_CustomResourceValidation(in *CustomResourceValidation, out *apiextensions.CustomResourceValidation, s conversion.Scope) error { if in.OpenAPIV3Schema != nil { in, out := &in.OpenAPIV3Schema, &out.OpenAPIV3Schema diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/zz_generated.deepcopy.go index 363e970dae6..669225fdf0d 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/zz_generated.deepcopy.go @@ -138,6 +138,15 @@ func (in *CustomResourceDefinitionSpec) DeepCopyInto(out *CustomResourceDefiniti (*in).DeepCopyInto(*out) } } + if in.Subresources != nil { + in, out := &in.Subresources, &out.Subresources + if *in == nil { + *out = nil + } else { + *out = new(CustomResourceSubresources) + (*in).DeepCopyInto(*out) + } + } return } @@ -175,6 +184,81 @@ func (in *CustomResourceDefinitionStatus) DeepCopy() *CustomResourceDefinitionSt return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CustomResourceSubresourceScale) DeepCopyInto(out *CustomResourceSubresourceScale) { + *out = *in + if in.LabelSelectorPath != nil { + in, out := &in.LabelSelectorPath, &out.LabelSelectorPath + if *in == nil { + *out = nil + } else { + *out = new(string) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceSubresourceScale. +func (in *CustomResourceSubresourceScale) DeepCopy() *CustomResourceSubresourceScale { + if in == nil { + return nil + } + out := new(CustomResourceSubresourceScale) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CustomResourceSubresourceStatus) DeepCopyInto(out *CustomResourceSubresourceStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceSubresourceStatus. +func (in *CustomResourceSubresourceStatus) DeepCopy() *CustomResourceSubresourceStatus { + if in == nil { + return nil + } + out := new(CustomResourceSubresourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CustomResourceSubresources) DeepCopyInto(out *CustomResourceSubresources) { + *out = *in + if in.Status != nil { + in, out := &in.Status, &out.Status + if *in == nil { + *out = nil + } else { + *out = new(CustomResourceSubresourceStatus) + **out = **in + } + } + if in.Scale != nil { + in, out := &in.Scale, &out.Scale + if *in == nil { + *out = nil + } else { + *out = new(CustomResourceSubresourceScale) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceSubresources. +func (in *CustomResourceSubresources) DeepCopy() *CustomResourceSubresources { + if in == nil { + return nil + } + out := new(CustomResourceSubresources) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CustomResourceValidation) DeepCopyInto(out *CustomResourceValidation) { *out = *in diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/zz_generated.deepcopy.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/zz_generated.deepcopy.go index f94bbae2c92..f14f179ee08 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/zz_generated.deepcopy.go @@ -138,6 +138,15 @@ func (in *CustomResourceDefinitionSpec) DeepCopyInto(out *CustomResourceDefiniti (*in).DeepCopyInto(*out) } } + if in.Subresources != nil { + in, out := &in.Subresources, &out.Subresources + if *in == nil { + *out = nil + } else { + *out = new(CustomResourceSubresources) + (*in).DeepCopyInto(*out) + } + } return } @@ -175,6 +184,81 @@ func (in *CustomResourceDefinitionStatus) DeepCopy() *CustomResourceDefinitionSt return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CustomResourceSubresourceScale) DeepCopyInto(out *CustomResourceSubresourceScale) { + *out = *in + if in.LabelSelectorPath != nil { + in, out := &in.LabelSelectorPath, &out.LabelSelectorPath + if *in == nil { + *out = nil + } else { + *out = new(string) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceSubresourceScale. +func (in *CustomResourceSubresourceScale) DeepCopy() *CustomResourceSubresourceScale { + if in == nil { + return nil + } + out := new(CustomResourceSubresourceScale) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CustomResourceSubresourceStatus) DeepCopyInto(out *CustomResourceSubresourceStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceSubresourceStatus. +func (in *CustomResourceSubresourceStatus) DeepCopy() *CustomResourceSubresourceStatus { + if in == nil { + return nil + } + out := new(CustomResourceSubresourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CustomResourceSubresources) DeepCopyInto(out *CustomResourceSubresources) { + *out = *in + if in.Status != nil { + in, out := &in.Status, &out.Status + if *in == nil { + *out = nil + } else { + *out = new(CustomResourceSubresourceStatus) + **out = **in + } + } + if in.Scale != nil { + in, out := &in.Scale, &out.Scale + if *in == nil { + *out = nil + } else { + *out = new(CustomResourceSubresourceScale) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceSubresources. +func (in *CustomResourceSubresources) DeepCopy() *CustomResourceSubresources { + if in == nil { + return nil + } + out := new(CustomResourceSubresources) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CustomResourceValidation) DeepCopyInto(out *CustomResourceValidation) { *out = *in diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/BUILD index fd3c45fc883..454689b3c47 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/BUILD @@ -16,7 +16,11 @@ go_library( ], importpath = "k8s.io/apiextensions-apiserver/pkg/apiserver", deps = [ + "//vendor/github.com/go-openapi/spec:go_default_library", + "//vendor/github.com/go-openapi/strfmt:go_default_library", + "//vendor/github.com/go-openapi/validate:go_default_library", "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/autoscaling/v1:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library", @@ -29,6 +33,7 @@ go_library( "//vendor/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/controller/finalizer:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/controller/status:go_default_library", + "//vendor/k8s.io/apiextensions-apiserver/pkg/features:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", @@ -60,7 +65,10 @@ go_library( "//vendor/k8s.io/apiserver/pkg/server:go_default_library", "//vendor/k8s.io/apiserver/pkg/server/storage:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/k8s.io/client-go/discovery:go_default_library", + "//vendor/k8s.io/client-go/scale:go_default_library", + "//vendor/k8s.io/client-go/scale/scheme/autoscalingv1:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library", "//vendor/k8s.io/client-go/util/workqueue:go_default_library", ], diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/BUILD index 84d77bd5bf7..de104e2f7a4 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/BUILD @@ -3,20 +3,30 @@ package(default_visibility = ["//visibility:public"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", + "go_test", ) go_library( name = "go_default_library", srcs = [ "etcd.go", + "registry.go", + "status_strategy.go", "strategy.go", + "validator.go", ], importpath = "k8s.io/apiextensions-apiserver/pkg/registry/customresource", deps = [ "//vendor/github.com/go-openapi/validate:go_default_library", + "//vendor/k8s.io/api/autoscaling/v1:go_default_library", + "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/validation:go_default_library", + "//vendor/k8s.io/apiextensions-apiserver/pkg/features:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/validation:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", @@ -24,11 +34,14 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", "//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", ], ) @@ -44,3 +57,27 @@ filegroup( srcs = [":package-srcs"], tags = ["automanaged"], ) + +go_test( + name = "go_default_xtest", + srcs = ["etcd_test.go"], + deps = [ + "//vendor/k8s.io/api/autoscaling/v1:go_default_library", + "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library", + "//vendor/k8s.io/apiextensions-apiserver/pkg/apiserver:go_default_library", + "//vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", + "//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library", + "//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library", + "//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library", + "//vendor/k8s.io/client-go/discovery:go_default_library", + ], +) diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/BUILD b/staging/src/k8s.io/apiextensions-apiserver/test/integration/BUILD index 84147b3010d..b9617e41a3e 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/BUILD @@ -11,6 +11,7 @@ go_test( "basic_test.go", "finalization_test.go", "registration_test.go", + "subresources_test.go", "validation_test.go", "yaml_test.go", ], @@ -19,17 +20,23 @@ go_test( "//vendor/github.com/coreos/etcd/clientv3:go_default_library", "//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/github.com/stretchr/testify/require:go_default_library", + "//vendor/k8s.io/api/autoscaling/v1:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/apiserver:go_default_library", + "//vendor/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library", + "//vendor/k8s.io/apiextensions-apiserver/pkg/features:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/test/integration/testserver:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", + "//vendor/k8s.io/apiserver/pkg/util/feature/testing:go_default_library", "//vendor/k8s.io/client-go/dynamic:go_default_library", ], ) diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/BUILD b/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/BUILD index 655dbc3573c..8c12d58d30d 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/BUILD @@ -28,7 +28,10 @@ go_library( "//vendor/k8s.io/apiserver/pkg/server:go_default_library", "//vendor/k8s.io/apiserver/pkg/server/options:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library", + "//vendor/k8s.io/client-go/discovery:go_default_library", "//vendor/k8s.io/client-go/dynamic:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + "//vendor/k8s.io/client-go/scale:go_default_library", ], )