[master] Auto-update dependencies (#268)

Produced via:
  `./hack/update-deps.sh --upgrade && ./hack/update-codegen.sh`
/assign n3wscott vagababov
/cc n3wscott vagababov
This commit is contained in:
Matt Moore 2020-05-07 09:23:45 -07:00 committed by GitHub
parent ca448db68d
commit f613680e15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 4325 additions and 12 deletions

4
go.mod
View File

@ -16,8 +16,8 @@ require (
k8s.io/client-go v11.0.1-0.20190805182717-6502b5e7b1b5+incompatible
k8s.io/code-generator v0.18.0
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a
knative.dev/pkg v0.0.0-20200506001744-478962f05e2b
knative.dev/test-infra v0.0.0-20200506045344-e71b1288c15c
knative.dev/pkg v0.0.0-20200507011344-2581370e4a37
knative.dev/test-infra v0.0.0-20200506231144-c8dd15bb7f0b
)
replace (

13
go.sum
View File

@ -1162,6 +1162,7 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0=
gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=
gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485 h1:OB/uP/Puiu5vS5QMRPrXCDWUPb+kt8f1KW8oQzFejQw=
@ -1368,21 +1369,17 @@ knative.dev/eventing-contrib v0.6.1-0.20190723221543-5ce18048c08b/go.mod h1:SnXZ
knative.dev/pkg v0.0.0-20191101194912-56c2594e4f11/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q=
knative.dev/pkg v0.0.0-20191111150521-6d806b998379/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q=
knative.dev/pkg v0.0.0-20200428194351-90fc61bae7f7/go.mod h1:o+e8OVEJKIuvXPsGVPIautjXgs05xbos7G+QMRjuUps=
knative.dev/pkg v0.0.0-20200504180943-4a2ba059b008 h1:ta/5dsSVJZuuilpa84232sHBiPAb2RZ5+rKmnQZvS1k=
knative.dev/pkg v0.0.0-20200504180943-4a2ba059b008/go.mod h1:1RvwKBbKqKYt5rgI4lfYdWCdtXgMxJY73QxPb3jZPC4=
knative.dev/pkg v0.0.0-20200505191044-3da93ebb24c2 h1:Qu2NlOHb9p3g+CSL/ok9+FySowN60URFEKRSXfWtDv4=
knative.dev/pkg v0.0.0-20200505191044-3da93ebb24c2/go.mod h1:Q6sL35DdGs8hIQZKdaCXJGgY8f90BmNBKSb8z6d/BTM=
knative.dev/pkg v0.0.0-20200506001744-478962f05e2b h1:SFCuEj+NeA8dVn4Ms1ymfF4FUru8jc7D56L17Co1qe8=
knative.dev/pkg v0.0.0-20200506001744-478962f05e2b/go.mod h1:9UQS6bJECqqFG0q9BPaATbcG78co0s9Q6Dzo/6mR4uI=
knative.dev/pkg v0.0.0-20200507011344-2581370e4a37 h1:1YJwMfDREOHhMFtNEOSDqGO4DvPwihfZmXldSUvXGU0=
knative.dev/pkg v0.0.0-20200507011344-2581370e4a37/go.mod h1:MUQe/bW75GmlVeyHmdKag77FJWQrNH3rxt2Q9E1JZJs=
knative.dev/test-infra v0.0.0-20200407185800-1b88cb3b45a5/go.mod h1:xcdUkMJrLlBswIZqL5zCuBFOC22WIPMQoVX1L35i0vQ=
knative.dev/test-infra v0.0.0-20200430225942-f7c1fafc1cde h1:QSzxFsf21WXNhODvh0jRKbFR+c5UI7WFjiISy/sMOLg=
knative.dev/test-infra v0.0.0-20200430225942-f7c1fafc1cde/go.mod h1:xcdUkMJrLlBswIZqL5zCuBFOC22WIPMQoVX1L35i0vQ=
knative.dev/test-infra v0.0.0-20200505052144-5ea2f705bb55 h1:Ajn44+eHHjPQL/BQicj8LMy8VTD2ypMCfHJuZVGEtew=
knative.dev/test-infra v0.0.0-20200505052144-5ea2f705bb55/go.mod h1:WqF1Azka+FxPZ20keR2zCNtiQA1MP9ZB4BH4HuI+SIU=
knative.dev/test-infra v0.0.0-20200505192244-75864c82db21 h1:SsvqMKpvrn7cl7UqRUIT90SXDowHzpzHwHaTu+wN70s=
knative.dev/test-infra v0.0.0-20200505192244-75864c82db21/go.mod h1:AqweEMgaMbb2xmYq9ZOPsH/lQ61qNx2XGr5tGltj5QU=
knative.dev/test-infra v0.0.0-20200506045344-e71b1288c15c h1:GfRICwJBY2VmbzFzu/se73+gsfKEkc83qlTBcohJvN0=
knative.dev/test-infra v0.0.0-20200506045344-e71b1288c15c/go.mod h1:aMif0KXL4g19YCYwsy4Ocjjz5xgPlseYV+B95Oo4JGE=
knative.dev/test-infra v0.0.0-20200506231144-c8dd15bb7f0b h1:7TSH69HYINRP6BE9pb0FLm37WwMZ1ukxh0ySSLBd8ac=
knative.dev/test-infra v0.0.0-20200506231144-c8dd15bb7f0b/go.mod h1:aMif0KXL4g19YCYwsy4Ocjjz5xgPlseYV+B95Oo4JGE=
modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
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.

View File

@ -0,0 +1,9 @@
module gomodules.xyz/jsonpatch/v2
go 1.12
require (
github.com/evanphx/json-patch v4.5.0+incompatible
github.com/pkg/errors v0.8.1 // indirect
github.com/stretchr/testify v1.3.0
)

View File

@ -0,0 +1,11 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=

View File

@ -0,0 +1,336 @@
package jsonpatch
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"strings"
)
var errBadJSONDoc = fmt.Errorf("invalid JSON Document")
type JsonPatchOperation = Operation
type Operation struct {
Operation string `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value,omitempty"`
}
func (j *Operation) Json() string {
b, _ := json.Marshal(j)
return string(b)
}
func (j *Operation) MarshalJSON() ([]byte, error) {
var b bytes.Buffer
b.WriteString("{")
b.WriteString(fmt.Sprintf(`"op":"%s"`, j.Operation))
b.WriteString(fmt.Sprintf(`,"path":"%s"`, j.Path))
// Consider omitting Value for non-nullable operations.
if j.Value != nil || j.Operation == "replace" || j.Operation == "add" {
v, err := json.Marshal(j.Value)
if err != nil {
return nil, err
}
b.WriteString(`,"value":`)
b.Write(v)
}
b.WriteString("}")
return b.Bytes(), nil
}
type ByPath []Operation
func (a ByPath) Len() int { return len(a) }
func (a ByPath) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByPath) Less(i, j int) bool { return a[i].Path < a[j].Path }
func NewPatch(operation, path string, value interface{}) Operation {
return Operation{Operation: operation, Path: path, Value: value}
}
// CreatePatch creates a patch as specified in http://jsonpatch.com/
//
// 'a' is original, 'b' is the modified document. Both are to be given as json encoded content.
// The function will return an array of JsonPatchOperations
//
// An error will be returned if any of the two documents are invalid.
func CreatePatch(a, b []byte) ([]Operation, error) {
var aI interface{}
var bI interface{}
err := json.Unmarshal(a, &aI)
if err != nil {
return nil, errBadJSONDoc
}
err = json.Unmarshal(b, &bI)
if err != nil {
return nil, errBadJSONDoc
}
return handleValues(aI, bI, "", []Operation{})
}
// Returns true if the values matches (must be json types)
// The types of the values must match, otherwise it will always return false
// If two map[string]interface{} are given, all elements must match.
func matchesValue(av, bv interface{}) bool {
if reflect.TypeOf(av) != reflect.TypeOf(bv) {
return false
}
switch at := av.(type) {
case string:
bt, ok := bv.(string)
if ok && bt == at {
return true
}
case float64:
bt, ok := bv.(float64)
if ok && bt == at {
return true
}
case bool:
bt, ok := bv.(bool)
if ok && bt == at {
return true
}
case map[string]interface{}:
bt, ok := bv.(map[string]interface{})
if !ok {
return false
}
for key := range at {
if !matchesValue(at[key], bt[key]) {
return false
}
}
for key := range bt {
if !matchesValue(at[key], bt[key]) {
return false
}
}
return true
case []interface{}:
bt, ok := bv.([]interface{})
if !ok {
return false
}
if len(bt) != len(at) {
return false
}
for key := range at {
if !matchesValue(at[key], bt[key]) {
return false
}
}
for key := range bt {
if !matchesValue(at[key], bt[key]) {
return false
}
}
return true
}
return false
}
// From http://tools.ietf.org/html/rfc6901#section-4 :
//
// Evaluation of each reference token begins by decoding any escaped
// character sequence. This is performed by first transforming any
// occurrence of the sequence '~1' to '/', and then transforming any
// occurrence of the sequence '~0' to '~'.
// TODO decode support:
// var rfc6901Decoder = strings.NewReplacer("~1", "/", "~0", "~")
var rfc6901Encoder = strings.NewReplacer("~", "~0", "/", "~1")
func makePath(path string, newPart interface{}) string {
key := rfc6901Encoder.Replace(fmt.Sprintf("%v", newPart))
if path == "" {
return "/" + key
}
if strings.HasSuffix(path, "/") {
return path + key
}
return path + "/" + key
}
// diff returns the (recursive) difference between a and b as an array of JsonPatchOperations.
func diff(a, b map[string]interface{}, path string, patch []Operation) ([]Operation, error) {
for key, bv := range b {
p := makePath(path, key)
av, ok := a[key]
// value was added
if !ok {
patch = append(patch, NewPatch("add", p, bv))
continue
}
// Types are the same, compare values
var err error
patch, err = handleValues(av, bv, p, patch)
if err != nil {
return nil, err
}
}
// Now add all deleted values as nil
for key := range a {
_, found := b[key]
if !found {
p := makePath(path, key)
patch = append(patch, NewPatch("remove", p, nil))
}
}
return patch, nil
}
func handleValues(av, bv interface{}, p string, patch []Operation) ([]Operation, error) {
{
at := reflect.TypeOf(av)
bt := reflect.TypeOf(bv)
if at == nil && bt == nil {
// do nothing
return patch, nil
} else if at == nil && bt != nil {
return append(patch, NewPatch("add", p, bv)), nil
} else if at != bt {
// If types have changed, replace completely (preserves null in destination)
return append(patch, NewPatch("replace", p, bv)), nil
}
}
var err error
switch at := av.(type) {
case map[string]interface{}:
bt := bv.(map[string]interface{})
patch, err = diff(at, bt, p, patch)
if err != nil {
return nil, err
}
case string, float64, bool:
if !matchesValue(av, bv) {
patch = append(patch, NewPatch("replace", p, bv))
}
case []interface{}:
bt := bv.([]interface{})
if isSimpleArray(at) && isSimpleArray(bt) {
patch = append(patch, compareEditDistance(at, bt, p)...)
} else {
n := min(len(at), len(bt))
for i := len(at) - 1; i >= n; i-- {
patch = append(patch, NewPatch("remove", makePath(p, i), nil))
}
for i := n; i < len(bt); i++ {
patch = append(patch, NewPatch("add", makePath(p, i), bt[i]))
}
for i := 0; i < n; i++ {
var err error
patch, err = handleValues(at[i], bt[i], makePath(p, i), patch)
if err != nil {
return nil, err
}
}
}
default:
panic(fmt.Sprintf("Unknown type:%T ", av))
}
return patch, nil
}
func isBasicType(a interface{}) bool {
switch a.(type) {
case string, float64, bool:
default:
return false
}
return true
}
func isSimpleArray(a []interface{}) bool {
for i := range a {
switch a[i].(type) {
case string, float64, bool:
default:
val := reflect.ValueOf(a[i])
if val.Kind() == reflect.Map {
for _, k := range val.MapKeys() {
av := val.MapIndex(k)
if av.Kind() == reflect.Ptr || av.Kind() == reflect.Interface {
if av.IsNil() {
continue
}
av = av.Elem()
}
if av.Kind() != reflect.String && av.Kind() != reflect.Float64 && av.Kind() != reflect.Bool {
return false
}
}
return true
}
return false
}
}
return true
}
// https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm
// Adapted from https://github.com/texttheater/golang-levenshtein
func compareEditDistance(s, t []interface{}, p string) []Operation {
m := len(s)
n := len(t)
d := make([][]int, m+1)
for i := 0; i <= m; i++ {
d[i] = make([]int, n+1)
d[i][0] = i
}
for j := 0; j <= n; j++ {
d[0][j] = j
}
for j := 1; j <= n; j++ {
for i := 1; i <= m; i++ {
if reflect.DeepEqual(s[i-1], t[j-1]) {
d[i][j] = d[i-1][j-1] // no op required
} else {
del := d[i-1][j] + 1
add := d[i][j-1] + 1
rep := d[i-1][j-1] + 1
d[i][j] = min(rep, min(add, del))
}
}
}
return backtrace(s, t, p, m, n, d)
}
func min(x int, y int) int {
if y < x {
return y
}
return x
}
func backtrace(s, t []interface{}, p string, i int, j int, matrix [][]int) []Operation {
if i > 0 && matrix[i-1][j]+1 == matrix[i][j] {
op := NewPatch("remove", makePath(p, i-1), nil)
return append([]Operation{op}, backtrace(s, t, p, i-1, j, matrix)...)
}
if j > 0 && matrix[i][j-1]+1 == matrix[i][j] {
op := NewPatch("add", makePath(p, i), t[j-1])
return append([]Operation{op}, backtrace(s, t, p, i, j-1, matrix)...)
}
if i > 0 && j > 0 && matrix[i-1][j-1]+1 == matrix[i][j] {
if isBasicType(s[0]) {
op := NewPatch("replace", makePath(p, i-1), t[j-1])
return append([]Operation{op}, backtrace(s, t, p, i-1, j-1, matrix)...)
}
p2, _ := handleValues(s[i-1], t[j-1], makePath(p, i-1), []Operation{})
return append(p2, backtrace(s, t, p, i-1, j-1, matrix)...)
}
if i > 0 && j > 0 && matrix[i-1][j-1] == matrix[i][j] {
return backtrace(s, t, p, i-1, j-1, matrix)
}
return []Operation{}
}

59
vendor/k8s.io/client-go/dynamic/interface.go generated vendored Normal file
View File

@ -0,0 +1,59 @@
/*
Copyright 2016 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 dynamic
import (
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/watch"
)
type Interface interface {
Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface
}
type ResourceInterface interface {
Create(obj *unstructured.Unstructured, options metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error)
Update(obj *unstructured.Unstructured, options metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error)
UpdateStatus(obj *unstructured.Unstructured, options metav1.UpdateOptions) (*unstructured.Unstructured, error)
Delete(name string, options *metav1.DeleteOptions, subresources ...string) error
DeleteCollection(options *metav1.DeleteOptions, listOptions metav1.ListOptions) error
Get(name string, options metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error)
List(opts metav1.ListOptions) (*unstructured.UnstructuredList, error)
Watch(opts metav1.ListOptions) (watch.Interface, error)
Patch(name string, pt types.PatchType, data []byte, options metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error)
}
type NamespaceableResourceInterface interface {
Namespace(string) ResourceInterface
ResourceInterface
}
// APIPathResolverFunc knows how to convert a groupVersion to its API path. The Kind field is optional.
// TODO find a better place to move this for existing callers
type APIPathResolverFunc func(kind schema.GroupVersionKind) string
// LegacyAPIPathResolverFunc can resolve paths properly with the legacy API.
// TODO find a better place to move this for existing callers
func LegacyAPIPathResolverFunc(kind schema.GroupVersionKind) string {
if len(kind.Group) == 0 {
return "/api"
}
return "/apis"
}

102
vendor/k8s.io/client-go/dynamic/scheme.go generated vendored Normal file
View File

@ -0,0 +1,102 @@
/*
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 dynamic
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"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"
)
var watchScheme = runtime.NewScheme()
var basicScheme = runtime.NewScheme()
var deleteScheme = runtime.NewScheme()
var parameterScheme = runtime.NewScheme()
var deleteOptionsCodec = serializer.NewCodecFactory(deleteScheme)
var dynamicParameterCodec = runtime.NewParameterCodec(parameterScheme)
var versionV1 = schema.GroupVersion{Version: "v1"}
func init() {
metav1.AddToGroupVersion(watchScheme, versionV1)
metav1.AddToGroupVersion(basicScheme, versionV1)
metav1.AddToGroupVersion(parameterScheme, versionV1)
metav1.AddToGroupVersion(deleteScheme, versionV1)
}
var watchJsonSerializerInfo = runtime.SerializerInfo{
MediaType: "application/json",
MediaTypeType: "application",
MediaTypeSubType: "json",
EncodesAsText: true,
Serializer: json.NewSerializer(json.DefaultMetaFactory, watchScheme, watchScheme, false),
PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, watchScheme, watchScheme, true),
StreamSerializer: &runtime.StreamSerializerInfo{
EncodesAsText: true,
Serializer: json.NewSerializer(json.DefaultMetaFactory, watchScheme, watchScheme, false),
Framer: json.Framer,
},
}
// watchNegotiatedSerializer is used to read the wrapper of the watch stream
type watchNegotiatedSerializer struct{}
var watchNegotiatedSerializerInstance = watchNegotiatedSerializer{}
func (s watchNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
return []runtime.SerializerInfo{watchJsonSerializerInfo}
}
func (s watchNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
return versioning.NewDefaultingCodecForScheme(watchScheme, encoder, nil, gv, nil)
}
func (s watchNegotiatedSerializer) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
return versioning.NewDefaultingCodecForScheme(watchScheme, nil, decoder, nil, gv)
}
// basicNegotiatedSerializer is used to handle discovery and error handling serialization
type basicNegotiatedSerializer struct{}
func (s basicNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
return []runtime.SerializerInfo{
{
MediaType: "application/json",
MediaTypeType: "application",
MediaTypeSubType: "json",
EncodesAsText: true,
Serializer: json.NewSerializer(json.DefaultMetaFactory, basicScheme, basicScheme, false),
PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, basicScheme, basicScheme, true),
StreamSerializer: &runtime.StreamSerializerInfo{
EncodesAsText: true,
Serializer: json.NewSerializer(json.DefaultMetaFactory, basicScheme, basicScheme, false),
Framer: json.Framer,
},
},
}
}
func (s basicNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
return versioning.NewDefaultingCodecForScheme(watchScheme, encoder, nil, gv, nil)
}
func (s basicNegotiatedSerializer) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
return versioning.NewDefaultingCodecForScheme(watchScheme, nil, decoder, nil, gv)
}

355
vendor/k8s.io/client-go/dynamic/simple.go generated vendored Normal file
View File

@ -0,0 +1,355 @@
/*
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 dynamic
import (
"fmt"
"io"
"k8s.io/apimachinery/pkg/api/meta"
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/runtime/serializer/streaming"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/rest"
)
type dynamicClient struct {
client *rest.RESTClient
}
var _ Interface = &dynamicClient{}
// ConfigFor returns a copy of the provided config with the
// appropriate dynamic client defaults set.
func ConfigFor(inConfig *rest.Config) *rest.Config {
config := rest.CopyConfig(inConfig)
config.AcceptContentTypes = "application/json"
config.ContentType = "application/json"
config.NegotiatedSerializer = basicNegotiatedSerializer{} // this gets used for discovery and error handling types
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return config
}
// NewForConfigOrDie creates a new Interface for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) Interface {
ret, err := NewForConfig(c)
if err != nil {
panic(err)
}
return ret
}
// NewForConfig creates a new dynamic client or returns an error.
func NewForConfig(inConfig *rest.Config) (Interface, error) {
config := ConfigFor(inConfig)
// for serializing the options
config.GroupVersion = &schema.GroupVersion{}
config.APIPath = "/if-you-see-this-search-for-the-break"
restClient, err := rest.RESTClientFor(config)
if err != nil {
return nil, err
}
return &dynamicClient{client: restClient}, nil
}
type dynamicResourceClient struct {
client *dynamicClient
namespace string
resource schema.GroupVersionResource
}
func (c *dynamicClient) Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface {
return &dynamicResourceClient{client: c, resource: resource}
}
func (c *dynamicResourceClient) Namespace(ns string) ResourceInterface {
ret := *c
ret.namespace = ns
return &ret
}
func (c *dynamicResourceClient) Create(obj *unstructured.Unstructured, opts metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error) {
outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
if err != nil {
return nil, err
}
name := ""
if len(subresources) > 0 {
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
name = accessor.GetName()
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
}
result := c.client.client.
Post().
AbsPath(append(c.makeURLSegments(name), subresources...)...).
Body(outBytes).
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Do()
if err := result.Error(); err != nil {
return nil, err
}
retBytes, err := result.Raw()
if err != nil {
return nil, err
}
uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
if err != nil {
return nil, err
}
return uncastObj.(*unstructured.Unstructured), nil
}
func (c *dynamicResourceClient) Update(obj *unstructured.Unstructured, opts metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error) {
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
name := accessor.GetName()
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
if err != nil {
return nil, err
}
result := c.client.client.
Put().
AbsPath(append(c.makeURLSegments(name), subresources...)...).
Body(outBytes).
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Do()
if err := result.Error(); err != nil {
return nil, err
}
retBytes, err := result.Raw()
if err != nil {
return nil, err
}
uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
if err != nil {
return nil, err
}
return uncastObj.(*unstructured.Unstructured), nil
}
func (c *dynamicResourceClient) UpdateStatus(obj *unstructured.Unstructured, opts metav1.UpdateOptions) (*unstructured.Unstructured, error) {
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
name := accessor.GetName()
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
if err != nil {
return nil, err
}
result := c.client.client.
Put().
AbsPath(append(c.makeURLSegments(name), "status")...).
Body(outBytes).
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Do()
if err := result.Error(); err != nil {
return nil, err
}
retBytes, err := result.Raw()
if err != nil {
return nil, err
}
uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
if err != nil {
return nil, err
}
return uncastObj.(*unstructured.Unstructured), nil
}
func (c *dynamicResourceClient) Delete(name string, opts *metav1.DeleteOptions, subresources ...string) error {
if len(name) == 0 {
return fmt.Errorf("name is required")
}
if opts == nil {
opts = &metav1.DeleteOptions{}
}
deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), opts)
if err != nil {
return err
}
result := c.client.client.
Delete().
AbsPath(append(c.makeURLSegments(name), subresources...)...).
Body(deleteOptionsByte).
Do()
return result.Error()
}
func (c *dynamicResourceClient) DeleteCollection(opts *metav1.DeleteOptions, listOptions metav1.ListOptions) error {
if opts == nil {
opts = &metav1.DeleteOptions{}
}
deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), opts)
if err != nil {
return err
}
result := c.client.client.
Delete().
AbsPath(c.makeURLSegments("")...).
Body(deleteOptionsByte).
SpecificallyVersionedParams(&listOptions, dynamicParameterCodec, versionV1).
Do()
return result.Error()
}
func (c *dynamicResourceClient) Get(name string, opts metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) {
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
result := c.client.client.Get().AbsPath(append(c.makeURLSegments(name), subresources...)...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).Do()
if err := result.Error(); err != nil {
return nil, err
}
retBytes, err := result.Raw()
if err != nil {
return nil, err
}
uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
if err != nil {
return nil, err
}
return uncastObj.(*unstructured.Unstructured), nil
}
func (c *dynamicResourceClient) List(opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
result := c.client.client.Get().AbsPath(c.makeURLSegments("")...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).Do()
if err := result.Error(); err != nil {
return nil, err
}
retBytes, err := result.Raw()
if err != nil {
return nil, err
}
uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
if err != nil {
return nil, err
}
if list, ok := uncastObj.(*unstructured.UnstructuredList); ok {
return list, nil
}
list, err := uncastObj.(*unstructured.Unstructured).ToList()
if err != nil {
return nil, err
}
return list, nil
}
func (c *dynamicResourceClient) Watch(opts metav1.ListOptions) (watch.Interface, error) {
internalGV := schema.GroupVersions{
{Group: c.resource.Group, Version: runtime.APIVersionInternal},
// always include the legacy group as a decoding target to handle non-error `Status` return types
{Group: "", Version: runtime.APIVersionInternal},
}
s := &rest.Serializers{
Encoder: watchNegotiatedSerializerInstance.EncoderForVersion(watchJsonSerializerInfo.Serializer, c.resource.GroupVersion()),
Decoder: watchNegotiatedSerializerInstance.DecoderToVersion(watchJsonSerializerInfo.Serializer, internalGV),
RenegotiatedDecoder: func(contentType string, params map[string]string) (runtime.Decoder, error) {
return watchNegotiatedSerializerInstance.DecoderToVersion(watchJsonSerializerInfo.Serializer, internalGV), nil
},
StreamingSerializer: watchJsonSerializerInfo.StreamSerializer.Serializer,
Framer: watchJsonSerializerInfo.StreamSerializer.Framer,
}
wrappedDecoderFn := func(body io.ReadCloser) streaming.Decoder {
framer := s.Framer.NewFrameReader(body)
return streaming.NewDecoder(framer, s.StreamingSerializer)
}
opts.Watch = true
return c.client.client.Get().AbsPath(c.makeURLSegments("")...).
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
WatchWithSpecificDecoders(wrappedDecoderFn, unstructured.UnstructuredJSONScheme)
}
func (c *dynamicResourceClient) Patch(name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error) {
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
result := c.client.client.
Patch(pt).
AbsPath(append(c.makeURLSegments(name), subresources...)...).
Body(data).
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Do()
if err := result.Error(); err != nil {
return nil, err
}
retBytes, err := result.Raw()
if err != nil {
return nil, err
}
uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
if err != nil {
return nil, err
}
return uncastObj.(*unstructured.Unstructured), nil
}
func (c *dynamicResourceClient) makeURLSegments(name string) []string {
url := []string{}
if len(c.resource.Group) == 0 {
url = append(url, "api")
} else {
url = append(url, "apis", c.resource.Group)
}
url = append(url, c.resource.Version)
if len(c.namespace) > 0 {
url = append(url, "namespaces", c.namespace)
}
url = append(url, c.resource.Resource)
if len(name) > 0 {
url = append(url, name)
}
return url
}

View File

@ -142,6 +142,11 @@ type conditionsImpl struct {
accessor ConditionsAccessor
}
// GetTopLevelConditionType is an accessor for the top-level happy condition.
func (r ConditionSet) GetTopLevelConditionType() ConditionType {
return r.happy
}
// Manage creates a ConditionManager from an accessor object using the original
// ConditionSet as a reference. Status must be a pointer to a struct.
func (r ConditionSet) Manage(status ConditionsAccessor) ConditionManager {

View File

@ -0,0 +1,325 @@
# Knative Duck Typing
![A Trojan Duck](images/Knative-Duck0.png)
**Figure 1:** How to integrate with Knative.
## Problem statement
In Knative, we want to support
[loose coupling](https://docs.google.com/presentation/d/1KxKAcIZyblkXbpdGVCgmzfDDIhqgUcwsa0zlABfvaXI/edit#slide=id.p)
of the building blocks we are releasing. We want users to be able to use these
building blocks together, but also support composing them with non-Knative
components as well.
Unlike Knatives
[pluggability story](https://docs.google.com/presentation/d/10KWynvAJYuOEWy69VBa6bHJVCqIsz1TNdEKosNvcpPY/edit#slide=id.p)
(for replacing subsystems within a building block), we do not want to require
that the systems with which we compose have **identical** APIs (distinct
implementations). However, we do need a way of accessing (reading / writing)
certain **_pieces_** of information in a structured way.
**Enter [duck typing](https://en.wikipedia.org/wiki/Duck_typing)**. We will
define a partial schema, to which resource authors will adhere if they want to
participate within certain contexts of Knative.
For instance, consider the partial schema:
```yaml
foo:
bar: <string>
```
Both of these resources implement the above duck type:
```yaml
baz: 1234
foo:
bar: asdf
blah:
blurp: true
```
```yaml
field: running out of ideas
foo:
bar: a different string
another: you get the point
```
### Reading duck-typed data
At a high-level, reading duck-typed data is very straightforward: using the
partial object schema deserialize the resource ignoring unknown fields. The
fields we care about can then be accessed through the structured object that
represents the duck type.
### Writing duck-typed data
How to write duck-typed data is less straightforward because we do not want to
clobber every field we do not know about. To accomplish this, we will lean on
Kubernetes well established patching model.
First, we read the resource we intend to modify as our duck type. Keeping a copy
of the original, we then modify the fields of this duck typed resource to
reflect the change we want. Lastly, we synthesize a JSON Patch of the changes
between the original and the final version and issue a Patch to the Kubernetes
API with the delta.
Since the duck type inherently contains a subset of the fields in the resource,
the resulting JSON Patch can only contain fields relevant to the resource.
## Example: Reading Knative-style Conditions
In Knative, we follow the Kubernetes API principles of using `conditions` as a
key part of our resources status, but we go a step further in
[defining particular conventions](https://github.com/knative/serving/blob/master/docs/spec/errors.md#error-conditions-and-reporting)
on how these are used.
To support this, we define:
```golang
type KResource struct {
        metav1.TypeMeta   `json:",inline"`
        metav1.ObjectMeta `json:"metadata,omitempty"`
        Status KResourceStatus `json:"status"`
}
type KResourceStatus struct {
        Conditions Conditions `json:"conditions,omitempty"`
}
type Conditions []Condition
type Condition struct {
  // structure adhering to K8s API principles
  ...
}
```
We can now deserialize and reason about the status of any Knative-compatible
resource using this partial schema.
## Example: Mutating Knative CRD Generations
In Knative, all of our resources define a `.spec.generation` field, which we use
in place of `.metadata.generation` because the latter was not properly managed
by Kubernetes (prior to 1.11 with `/status` subresource). We manage bumping this
generation field in our webhook if and only if the `.spec` changed.
To support this, we define:
```golang
type Generational struct {
        metav1.TypeMeta   `json:",inline"`
        metav1.ObjectMeta `json:"metadata,omitempty"`
        Spec GenerationalSpec `json:"spec"`
}
type GenerationalSpec struct {
        Generation Generation `json:"generation,omitempty"`
}
type Generation int64
```
Using this our webhook can read the current resources generation, increment it,
and generate a patch to apply it.
## Example: Mutating Core Kubernetes Resources
Kubernetes already uses duck typing, in a way. Consider that `Deployment`,
`ReplicaSet`, `DaemonSet`, `StatefulSet`, and `Job` all embed a
`corev1.PodTemplateSpec` at the exact path: `.spec.template`.
Consider the example duck type:
```yaml
type PodSpecable corev1.PodTemplateSpec
type WithPod struct { metav1.TypeMeta   `json:",inline"` metav1.ObjectMeta
`json:"metadata,omitempty"`
Spec WithPodSpec `json:"spec,omitempty"` }
type WithPodSpec struct { Template PodSpecable `json:"template,omitempty"` }
```
Using this, we can access the PodSpec of arbitrary higher-level Kubernetes
resources in a very structured way and generate patches to mutate them.
[See examples](https://github.com/knative/pkg/blob/07104dad53e803457a95306e5b1322024bd69af3/apis/duck/podspec_test.go#L49-L53).
_You can also see a sample controller that reconciles duck-typed resources
[here](https://github.com/mattmoor/cachier)._
## Conventions
Each of our duck types will consist of a single structured field that must be
enclosed within the containing resource in a particular way.
1. This structured field will be named <code>Foo<strong>able</strong></code>,
2. <code>Fooable</code> will be directly included via a field named
<code>fooable</code>,
3. Additional skeletal layers around <code>Fooable</code> will be defined to
fully define <code>Fooable</code>s position within complete resources.
_You can see parts of these in the examples above, however, those special cases
have been exempted from the first condition for legacy compatibility reasons._
For example:
1. `type Conditions []Condition`
2. <code>Conditions Conditions
`json:"<strong>conditions</strong>,omitempty"`</code>
3. <code>KResource -> KResourceStatus -> Conditions</code>
## Supporting Mechanics
We will provide a number of tools to enable working with duck types without
blowing off feet.
### Verification
To verify that a particular resource implements a particular duck type, resource
authors are strongly encouraged to add the following as test code adjacent to
resource definitions.
`myresource_types.go`:
```golang
package v1alpha1
type MyResource struct {
...
}
```
`myresource_types_test.go`:
```golang
package v1alpha1
import (
"testing"
// This is where supporting tools for duck-typing will live.
"github.com/knative/pkg/apis/duck"
// This is where Knative-provided duck types will live.
duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
)
// This verifies that MyResource contains all the necessary fields for the
// given implementable duck type.
func TestType(t *testing.T) {
err := duck.VerifyType(&MyResource{}, &duckv1alpha1.Conditions{})
if err != nil {
t.Errorf("VerifyType() = %v", err)
}
}
```
\_This call will create a fully populated instance of the skeletal resource
containing the Conditions and ensure that the fields can 100% roundtrip through
<code>MyResource</code>.</em>
### Patching
To produce a patch of a particular resource modification suitable for use with
<code>k8s.io/client-[go/dynamic](https://goto.google.com/dynamic)</code>,
developers can write:
```golang
before := …
after := before.DeepCopy()
// modify "after"
patch, err := duck.CreatePatch(before, after)
// check err
bytes, err := patch.MarshalJSON()
// check err
dynamicClient.Patch(bytes)
```
### Informers / Listers
To be able to efficiently access / monitor arbitrary duck-typed resources, we
want to be able to produce an Informer / Lister for interpreting particular
resource groups as a particular duck type.
To facilitate this, we provide several composable implementations of
`duck.InformerFactory`.
```golang
type InformerFactory interface {
        // Get an informer/lister pair for the given resource group.
        Get(GroupVersionResource) (SharedIndexInformer, GenericLister, error)
}
// This produces informer/lister pairs that interpret objects in the resource group
// as the provided duck "Type"
dif := &duck.TypedInformerFactory{
        Client:       dynaClient,
        Type:         &duckv1alpha1.Foo{},
        ResyncPeriod: 30 * time.Second,
        StopChannel:  stopCh,
}
// This registers the provided EventHandler with the informer each time an
// informer/lister pair is produced.
eif := &duck.EnqueueInformerFactory{
Delegate: dif,
EventHandler: cache.ResourceEventHandlerFuncs{
AddFunc: impl.EnqueueControllerOf,
UpdateFunc: controller.PassNew(impl.EnqueueControllerOf),
},
}
// This caches informer/lister pairs so that we only produce one for each GVR.
cif := &duck.CachedInformerFactory{
Delegate: eif,
}
```
### Trackers
Informers are great when you have something like an `OwnerReference` to key off
of for the association (e.g. `impl.EnqueueControllerOf`), however, when the
association is looser e.g. `corev1.ObjectReference`, then we need a way of
configuring a reconciliation trigger for the cross-reference.
For this (generally) we have the `knative/pkg/tracker` package. Here is how it
is used with duck types:
```golang
        c := &Reconciler{
                Base:             reconciler.NewBase(opt, controllerAgentName),
                ...
        }
        impl := controller.NewImpl(c, c.Logger, "Revisions")
// Calls to Track create a 30 minute lease before they must be renewed.
// Coordinate this value with controller resync periods.
        t := tracker.New(impl.EnqueueKey, 30*time.Minute)
        cif := &duck.CachedInformerFactory{
                Delegate: &duck.EnqueueInformerFactory{
                        Delegate: buildInformerFactory,
                        EventHandler: cache.ResourceEventHandlerFuncs{
                                AddFunc:    t.OnChanged,
                                UpdateFunc: controller.PassNew(t.OnChanged),
                        },
                },
        }
        // Now use: c.buildInformerFactory.Get() to access ObjectReferences.
        c.buildInformerFactory = buildInformerFactory
        // Now use: c.tracker.Track(rev.Spec.BuildRef, rev) to queue rev
        // each time rev.Spec.BuildRef changes.
        c.tracker = t
```

View File

@ -0,0 +1,128 @@
# Duck Types
Knative leverages duck-typing to interact with resources inside of Kubernetes
without explicit knowlage of the full resource shape. `knative/pkg` defines two
duck types that are used throughout Knative: `Addressable` and `Source`.
For APIs leveraging `ObjectReference`, the context of the resource in question
identifies the duck-type. To enable the case where no `ObjectRefrence` is used,
we have labeled the Custom Resource Definition with the duck-type. Those labels
are as follows:
| Label | Duck-Type |
| ----------------------------------- | ----------------------------------------------------------------------------- |
| `duck.knative.dev/addressable=true` | [Addressable](https://godoc.org/knative.dev/pkg/apis/duck/v1#AddressableType) |
| `duck.knative.dev/binding=true` | [Binding](https://godoc.org/knative.dev/pkg/apis/duck/v1alpha1#Binding) |
| `duck.knative.dev/source=true` | [Source](https://godoc.org/knative.dev/pkg/apis/duck/v1#Source) |
## Addressable Shape
Addressable is expected to be the following shape:
```yaml
apiVersion: group/version
kind: Kind
status:
address:
url: http://host/path?query
```
## Binding Shape
Binding is expected to be in the following shape:
(with direct subject)
```yaml
apiVersion: group/version
kind: Kind
spec:
subject:
apiVersion: group/version
kind: SomeKind
namespace: the-namespace
name: a-name
```
(with indirect subject)
```yaml
apiVersion: group/version
kind: Kind
spec:
subject:
apiVersion: group/version
kind: SomeKind
namespace: the-namespace
selector:
matchLabels:
key: value
```
## Source Shape
Source is expected to be in the following shape:
(with ref sink)
```yaml
apiVersion: group/version
kind: Kind
spec:
sink:
ref:
apiVersion: group/version
kind: AnAddressableKind
name: a-name
ceOverrides:
extensions:
key: value
status:
observedGeneration: 1
conditions:
- type: Ready
status: "True"
sinkUri: http://host
```
(with uri sink)
```yaml
apiVersion: group/version
kind: Kind
spec:
sink:
uri: http://host/path?query
ceOverrides:
extensions:
key: value
status:
observedGeneration: 1
conditions:
- type: Ready
status: "True"
sinkUri: http://host/path?query
```
(with ref and uri sink)
```yaml
apiVersion: group/version
kind: Kind
spec:
sink:
ref:
apiVersion: group/version
kind: AnAddressableKind
name: a-name
uri: /path?query
ceOverrides:
extensions:
key: value
status:
observedGeneration: 1
conditions:
- type: Ready
status: "True"
sinkUri: http://host/path?query
```

View File

@ -0,0 +1,100 @@
/*
Copyright 2018 The Knative 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 duck
import (
"sync"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/tools/cache"
)
// CachedInformerFactory implements InformerFactory by delegating to another
// InformerFactory, but memoizing the results.
type CachedInformerFactory struct {
Delegate InformerFactory
m sync.Mutex
cache map[schema.GroupVersionResource]*informerCache
}
// Check that CachedInformerFactory implements InformerFactory.
var _ InformerFactory = (*CachedInformerFactory)(nil)
// Get implements InformerFactory.
func (cif *CachedInformerFactory) Get(gvr schema.GroupVersionResource) (cache.SharedIndexInformer, cache.GenericLister, error) {
cif.m.Lock()
if cif.cache == nil {
cif.cache = make(map[schema.GroupVersionResource]*informerCache)
}
ic, ok := cif.cache[gvr]
if !ok {
ic = &informerCache{}
ic.init = func() {
ic.Lock()
defer ic.Unlock()
// double-checked lock to ensure we call the Delegate
// only once even if multiple goroutines end up inside
// init() simultaneously
if ic.hasInformer() {
return
}
ic.inf, ic.lister, ic.err = cif.Delegate.Get(gvr)
}
cif.cache[gvr] = ic
}
// If this were done via "defer", then TestDifferentGVRs will fail.
cif.m.Unlock()
// The call to the delegate could be slow because it syncs informers, so do
// this outside of the main lock.
return ic.Get()
}
type informerCache struct {
sync.RWMutex
init func()
inf cache.SharedIndexInformer
lister cache.GenericLister
err error
}
// Get returns the cached informer. If it does not yet exist, we first try to
// acquire one by executing the cache's init function.
func (ic *informerCache) Get() (cache.SharedIndexInformer, cache.GenericLister, error) {
if !ic.initialized() {
ic.init()
}
return ic.inf, ic.lister, ic.err
}
func (ic *informerCache) initialized() bool {
ic.RLock()
defer ic.RUnlock()
return ic.hasInformer()
}
func (ic *informerCache) hasInformer() bool {
return ic.inf != nil && ic.lister != nil
}

View File

@ -0,0 +1,33 @@
/*
Copyright 2019 The Knative 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 duck
const (
// BindingExcludeLabel is a label that is placed on namespaces and
// resources to exclude them from consideration when binding things.
// It is critical that bindings dealing with Deployments label their
// controller Deployment (or enclosing namespace). If you do not
// specify this label, they are considered for binding (i.e. you opt-in
// to getting everything considered for bindings). This is the default.
BindingExcludeLabel = "bindings.knative.dev/exclude"
// BindingIncludeLabel is a label that is placed on namespaces and
// resources to include them in consideration when binding things.
// This means that you have to explicitly label the namespaces/resources
// for consideration for bindings.
BindingIncludeLabel = "bindings.knative.dev/include"
)

23
vendor/knative.dev/pkg/apis/duck/doc.go vendored Normal file
View File

@ -0,0 +1,23 @@
/*
Copyright 2018 The Knative 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 duck defines logic for defining and consuming "duck typed"
// Kubernetes resources. Producers define partial resource definitions
// that resource authors may choose to implement to interoperate with
// consumers of these "duck typed" interfaces.
// For more information see:
// https://docs.google.com/document/d/16j8C91jML4fQRQPhnHihNJUJDcbvW0RM1YAX2REHgyY/edit#
package duck

View File

@ -0,0 +1,44 @@
/*
Copyright 2018 The Knative 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 duck
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/tools/cache"
)
// EnqueueInformerFactory implements InformerFactory by delegating to another
// InformerFactory, but attaching a ResourceEventHandler to the informer.
type EnqueueInformerFactory struct {
Delegate InformerFactory
EventHandler cache.ResourceEventHandler
}
// Check that EnqueueInformerFactory implements InformerFactory.
var _ InformerFactory = (*EnqueueInformerFactory)(nil)
// Get implements InformerFactory.
func (cif *EnqueueInformerFactory) Get(gvr schema.GroupVersionResource) (cache.SharedIndexInformer, cache.GenericLister, error) {
inf, lister, err := cif.Delegate.Get(gvr)
if err != nil {
return nil, nil, err
}
// If there is an informer, attach our event handler.
inf.AddEventHandler(cif.EventHandler)
return inf, lister, nil
}

View File

@ -0,0 +1,69 @@
/*
Copyright 2018 The Knative 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 duck
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/tools/cache"
"knative.dev/pkg/kmeta"
"knative.dev/pkg/tracker"
)
// InformerFactory is used to create Informer/Lister pairs for a schema.GroupVersionResource
type InformerFactory interface {
// Get returns a synced Informer/Lister pair for the provided schema.GroupVersionResource.
Get(schema.GroupVersionResource) (cache.SharedIndexInformer, cache.GenericLister, error)
}
// OneOfOurs is the union of our Accessor interface and the OwnerRefable interface
// that is implemented by our resources that implement the kmeta.Accessor.
type OneOfOurs interface {
kmeta.Accessor
kmeta.OwnerRefable
}
// BindableStatus is the interface that the .status of Bindable resources must
// implement to work smoothly with our BaseReconciler.
type BindableStatus interface {
// InitializeConditions seeds the resource's status.conditions field
// with all of the conditions that this Binding surfaces.
InitializeConditions()
// MarkBindingAvailable notes that this Binding has been properly
// configured.
MarkBindingAvailable()
// MarkBindingUnavailable notes the provided reason for why the Binding
// has failed.
MarkBindingUnavailable(reason string, message string)
// SetObservedGeneration updates the .status.observedGeneration to the
// provided generation value.
SetObservedGeneration(int64)
}
// Bindable may be implemented by Binding resources to use shared libraries.
type Bindable interface {
OneOfOurs
// GetSubject returns the standard Binding duck's "Subject" field.
GetSubject() tracker.Reference
// GetBindingStatus returns the status of the Binding, which must
// implement BindableStatus.
GetBindingStatus() BindableStatus
}

View File

@ -0,0 +1,73 @@
/*
Copyright 2018 The Knative 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 duck
import (
"encoding/json"
jsonmergepatch "github.com/evanphx/json-patch"
jsonpatch "gomodules.xyz/jsonpatch/v2"
)
func marshallBeforeAfter(before, after interface{}) ([]byte, []byte, error) {
rawBefore, err := json.Marshal(before)
if err != nil {
return nil, nil, err
}
rawAfter, err := json.Marshal(after)
if err != nil {
return rawBefore, nil, err
}
return rawBefore, rawAfter, nil
}
// CreateMergePatch creates a json merge patch as specified in
// http://tools.ietf.org/html/draft-ietf-appsawg-json-merge-patch-07
func CreateMergePatch(before, after interface{}) ([]byte, error) {
rawBefore, rawAfter, err := marshallBeforeAfter(before, after)
if err != nil {
return nil, err
}
return jsonmergepatch.CreateMergePatch(rawBefore, rawAfter)
}
// CreateBytePatch is a helper function that creates the same content as
// CreatePatch, but returns in []byte format instead of JSONPatch.
func CreateBytePatch(before, after interface{}) ([]byte, error) {
patch, err := CreatePatch(before, after)
if err != nil {
return nil, err
}
return patch.MarshalJSON()
}
// CreatePatch creates a patch as specified in http://jsonpatch.com/
func CreatePatch(before, after interface{}) (JSONPatch, error) {
rawBefore, rawAfter, err := marshallBeforeAfter(before, after)
if err != nil {
return nil, err
}
return jsonpatch.CreatePatch(rawBefore, rawAfter)
}
type JSONPatch []jsonpatch.JsonPatchOperation
func (p JSONPatch) MarshalJSON() ([]byte, error) {
return json.Marshal([]jsonpatch.JsonPatchOperation(p))
}

View File

@ -0,0 +1,74 @@
/*
Copyright 2018 The Knative 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 duck
import (
"sync"
"k8s.io/apimachinery/pkg/watch"
)
// NewProxyWatcher is based on the same concept from Kubernetes apimachinery in 1.12 here:
// https://github.com/kubernetes/apimachinery/blob/c6dd271be/pkg/watch/watch.go#L272
// Replace this copy once we've update our client libraries.
// proxyWatcher lets you wrap your channel in watch.Interface. Threadsafe.
type proxyWatcher struct {
result chan watch.Event
stopCh chan struct{}
mutex sync.Mutex
stopped bool
}
var _ watch.Interface = (*proxyWatcher)(nil)
// NewProxyWatcher creates new proxyWatcher by wrapping a channel
func NewProxyWatcher(ch chan watch.Event) watch.Interface {
return &proxyWatcher{
result: ch,
stopCh: make(chan struct{}),
stopped: false,
}
}
// Stop implements Interface
func (pw *proxyWatcher) Stop() {
pw.mutex.Lock()
defer pw.mutex.Unlock()
if !pw.stopped {
pw.stopped = true
close(pw.stopCh)
}
}
// Stopping returns true if Stop() has been called
func (pw *proxyWatcher) Stopping() bool {
pw.mutex.Lock()
defer pw.mutex.Unlock()
return pw.stopped
}
// ResultChan implements watch.Interface
func (pw *proxyWatcher) ResultChan() <-chan watch.Event {
return pw.result
}
// StopChan returns stop channel
func (pw *proxyWatcher) StopChan() <-chan struct{} {
return pw.stopCh
}

View File

@ -0,0 +1,21 @@
/*
Copyright 2018 The Knative 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 duck
const (
GroupName = "duck.knative.dev"
)

View File

@ -0,0 +1,139 @@
/*
Copyright 2018 The Knative 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 duck
import (
"fmt"
"net/http"
"time"
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/watch"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/tools/cache"
"knative.dev/pkg/apis"
)
// TypedInformerFactory implements InformerFactory such that the elements
// tracked by the informer/lister have the type of the canonical "obj".
type TypedInformerFactory struct {
Client dynamic.Interface
Type apis.Listable
ResyncPeriod time.Duration
StopChannel <-chan struct{}
}
// Check that TypedInformerFactory implements InformerFactory.
var _ InformerFactory = (*TypedInformerFactory)(nil)
// Get implements InformerFactory.
func (dif *TypedInformerFactory) Get(gvr schema.GroupVersionResource) (cache.SharedIndexInformer, cache.GenericLister, error) {
// Avoid error cases, like the GVR does not exist.
// It is not a full check. Some RBACs might sneak by, but the window is very small.
if _, err := dif.Client.Resource(gvr).List(metav1.ListOptions{}); err != nil {
return nil, nil, err
}
listObj := dif.Type.GetListType()
lw := &cache.ListWatch{
ListFunc: asStructuredLister(dif.Client.Resource(gvr).List, listObj),
WatchFunc: AsStructuredWatcher(dif.Client.Resource(gvr).Watch, dif.Type),
}
inf := cache.NewSharedIndexInformer(lw, dif.Type, dif.ResyncPeriod, cache.Indexers{
cache.NamespaceIndex: cache.MetaNamespaceIndexFunc,
})
lister := cache.NewGenericLister(inf.GetIndexer(), gvr.GroupResource())
go inf.Run(dif.StopChannel)
if ok := cache.WaitForCacheSync(dif.StopChannel, inf.HasSynced); !ok {
return nil, nil, fmt.Errorf("failed starting shared index informer for %v with type %T", gvr, dif.Type)
}
return inf, lister, nil
}
type unstructuredLister func(metav1.ListOptions) (*unstructured.UnstructuredList, error)
func asStructuredLister(ulist unstructuredLister, listObj runtime.Object) cache.ListFunc {
return func(opts metav1.ListOptions) (runtime.Object, error) {
ul, err := ulist(opts)
if err != nil {
return nil, err
}
res := listObj.DeepCopyObject()
if err := FromUnstructured(ul, res); err != nil {
return nil, err
}
return res, nil
}
}
// AsStructuredWatcher is public for testing only.
// TODO(mattmoor): Move tests for this to `package duck` and make private.
func AsStructuredWatcher(wf cache.WatchFunc, obj runtime.Object) cache.WatchFunc {
return func(lo metav1.ListOptions) (watch.Interface, error) {
uw, err := wf(lo)
if err != nil {
return nil, err
}
structuredCh := make(chan watch.Event)
go func() {
defer close(structuredCh)
unstructuredCh := uw.ResultChan()
for ue := range unstructuredCh {
unstructuredObj, ok := ue.Object.(*unstructured.Unstructured)
if !ok {
// If it isn't an unstructured object, then forward the
// event as-is. This is likely to happen when the event's
// Type is an Error.
structuredCh <- ue
continue
}
structuredObj := obj.DeepCopyObject()
err := FromUnstructured(unstructuredObj, structuredObj)
if err != nil {
// Pass back an error indicating that the object we got
// was invalid.
structuredCh <- watch.Event{
Type: watch.Error,
Object: &metav1.Status{
Status: metav1.StatusFailure,
Code: http.StatusUnprocessableEntity,
Reason: metav1.StatusReasonInvalid,
Message: err.Error(),
},
}
continue
}
// Send the structured event.
structuredCh <- watch.Event{
Type: ue.Type,
Object: structuredObj,
}
}
}()
return NewProxyWatcher(structuredCh), nil
}
}

View File

@ -0,0 +1,62 @@
/*
Copyright 2018 The Knative 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 duck
import (
"encoding/json"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// ToUnstructured takes an instance of a OneOfOurs compatible type and
// converts it to unstructured.Unstructured. We take OneOfOurs in place
// or runtime.Object because sometimes we get resources that do not have their
// TypeMeta populated but that is required for unstructured.Unstructured to
// deserialize things, so we leverage our content-agnostic GroupVersionKind()
// method to populate this as-needed (in a copy, so that we don't modify the
// informer's copy, if that is what we are passed).
func ToUnstructured(desired OneOfOurs) (*unstructured.Unstructured, error) {
// If the TypeMeta is not populated, then unmarshalling will fail, so ensure
// the TypeMeta is populated. See also EnsureTypeMeta.
if gvk := desired.GroupVersionKind(); gvk.Version == "" || gvk.Kind == "" {
gvk = desired.GetGroupVersionKind()
desired = desired.DeepCopyObject().(OneOfOurs)
desired.SetGroupVersionKind(gvk)
}
// Convert desired to unstructured.Unstructured
b, err := json.Marshal(desired)
if err != nil {
return nil, err
}
ud := &unstructured.Unstructured{}
if err := json.Unmarshal(b, ud); err != nil {
return nil, err
}
return ud, nil
}
// FromUnstructured takes unstructured object from (say from client-go/dynamic) and
// converts it into our duck types.
func FromUnstructured(obj json.Marshaler, target interface{}) error {
// Use the unstructured marshaller to ensure it's proper JSON
raw, err := obj.MarshalJSON()
if err != nil {
return err
}
return json.Unmarshal(raw, &target)
}

View File

@ -0,0 +1,115 @@
/*
Copyright 2019 The Knative 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 v1
import (
"context"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"knative.dev/pkg/apis"
"knative.dev/pkg/apis/duck"
)
// +genduck
// Addressable provides a generic mechanism for a custom resource
// definition to indicate a destination for message delivery.
//
// Addressable is the schema for the destination information. This is
// typically stored in the object's `status`, as this information may
// be generated by the controller.
type Addressable struct {
URL *apis.URL `json:"url,omitempty"`
}
var (
// Addressable is an Implementable "duck type".
_ duck.Implementable = (*Addressable)(nil)
// Addressable is a Convertible type.
_ apis.Convertible = (*Addressable)(nil)
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// AddressableType is a skeleton type wrapping Addressable in the manner we expect
// resource writers defining compatible resources to embed it. We will
// typically use this type to deserialize Addressable ObjectReferences and
// access the Addressable data. This is not a real resource.
type AddressableType struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Status AddressStatus `json:"status"`
}
// AddressStatus shows how we expect folks to embed Addressable in
// their Status field.
type AddressStatus struct {
Address *Addressable `json:"address,omitempty"`
}
var (
// Verify AddressableType resources meet duck contracts.
_ duck.Populatable = (*AddressableType)(nil)
_ apis.Listable = (*AddressableType)(nil)
)
// GetFullType implements duck.Implementable
func (*Addressable) GetFullType() duck.Populatable {
return &AddressableType{}
}
// ConvertTo implements apis.Convertible
func (a *Addressable) ConvertTo(ctx context.Context, to apis.Convertible) error {
return fmt.Errorf("v1 is the highest known version, got: %T", to)
}
// ConvertFrom implements apis.Convertible
func (a *Addressable) ConvertFrom(ctx context.Context, from apis.Convertible) error {
return fmt.Errorf("v1 is the highest known version, got: %T", from)
}
// Populate implements duck.Populatable
func (t *AddressableType) Populate() {
t.Status = AddressStatus{
&Addressable{
// Populate ALL fields
URL: &apis.URL{
Scheme: "http",
Host: "foo.com",
},
},
}
}
// GetListType implements apis.Listable
func (*AddressableType) GetListType() runtime.Object {
return &AddressableTypeList{}
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// AddressableTypeList is a list of AddressableType resources
type AddressableTypeList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []AddressableType `json:"items"`
}

View File

@ -0,0 +1,79 @@
/*
Copyright 2019 The Knative 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 v1
import (
"context"
"knative.dev/pkg/apis"
)
// Destination represents a target of an invocation over HTTP.
type Destination struct {
// Ref points to an Addressable.
// +optional
Ref *KReference `json:"ref,omitempty"`
// URI can be an absolute URL(non-empty scheme and non-empty host) pointing to the target or a relative URI. Relative URIs will be resolved using the base URI retrieved from Ref.
// +optional
URI *apis.URL `json:"uri,omitempty"`
}
// Validate the Destination has all the necessary fields and check the
// Namespace matches that of the parent object (using apis.ParentMeta).
func (dest *Destination) Validate(ctx context.Context) *apis.FieldError {
if dest == nil {
return nil
}
return ValidateDestination(ctx, *dest).ViaField(apis.CurrentField)
}
// ValidateDestination validates Destination.
func ValidateDestination(ctx context.Context, dest Destination) *apis.FieldError {
ref := dest.Ref
uri := dest.URI
if ref == nil && uri == nil {
return apis.ErrGeneric("expected at least one, got none", "ref", "uri")
}
if ref != nil && uri != nil && uri.URL().IsAbs() {
return apis.ErrGeneric("Absolute URI is not allowed when Ref or [apiVersion, kind, name] is present", "[apiVersion, kind, name]", "ref", "uri")
}
// IsAbs() check whether the URL has a non-empty scheme. Besides the non-empty scheme, we also require uri has a non-empty host
if ref == nil && uri != nil && (!uri.URL().IsAbs() || uri.Host == "") {
return apis.ErrInvalidValue("Relative URI is not allowed when Ref and [apiVersion, kind, name] is absent", "uri")
}
if ref != nil && uri == nil {
return ref.Validate(ctx).ViaField("ref")
}
return nil
}
// GetRef gets the KReference from this Destination, if one is present. If no ref is present,
// then nil is returned.
func (dest *Destination) GetRef() *KReference {
if dest == nil {
return nil
}
return dest.Ref
}
func (d *Destination) SetDefaults(ctx context.Context) {
if d.Ref != nil && d.Ref.Namespace == "" {
d.Ref.Namespace = apis.ParentMeta(ctx).Namespace
}
}

View File

@ -0,0 +1,23 @@
/*
Copyright 2019 The Knative 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.
*/
// Api versions allow the api contract for a resource to be changed while keeping
// backward compatibility by support multiple concurrent versions
// of the same resource
// +k8s:deepcopy-gen=package
// +groupName=duck.knative.dev
package v1

View File

@ -0,0 +1,88 @@
/*
Copyright 2020 The Knative 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 v1
import (
"context"
"fmt"
"knative.dev/pkg/apis"
)
// KReference contains enough information to refer to another object.
// It's a trimmed down version of corev1.ObjectReference.
type KReference struct {
// Kind of the referent.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
Kind string `json:"kind"`
// Namespace of the referent.
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
// This is optional field, it gets defaulted to the object holding it if left out.
// +optional
Namespace string `json:"namespace,omitempty"`
// Name of the referent.
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
Name string `json:"name"`
// API version of the referent.
APIVersion string `json:"apiVersion"`
}
func (kr *KReference) Validate(ctx context.Context) *apis.FieldError {
var errs *apis.FieldError
if kr == nil {
return errs.Also(apis.ErrMissingField("name")).
Also(apis.ErrMissingField("apiVersion")).
Also(apis.ErrMissingField("kind"))
}
if kr.Name == "" {
errs = errs.Also(apis.ErrMissingField("name"))
}
if kr.APIVersion == "" {
errs = errs.Also(apis.ErrMissingField("apiVersion"))
}
if kr.Kind == "" {
errs = errs.Also(apis.ErrMissingField("kind"))
}
// Only if namespace is empty validate it. This is to deal with legacy
// objects in the storage that may now have the namespace filled in.
// Because things get defaulted in other cases, moving forward the
// kr.Namespace will not be empty.
if kr.Namespace != "" {
if !apis.IsDifferentNamespaceAllowed(ctx) {
parentNS := apis.ParentMeta(ctx).Namespace
if parentNS != "" && kr.Namespace != parentNS {
errs = errs.Also(&apis.FieldError{
Message: "mismatched namespaces",
Paths: []string{"namespace"},
Details: fmt.Sprintf("parent namespace: %q does not match ref: %q", parentNS, kr.Namespace),
})
}
}
}
return errs
}
// SetDefaults sets the default values on the KReference.
func (kr *KReference) SetDefaults(ctx context.Context) {
if kr.Namespace == "" {
kr.Namespace = apis.ParentMeta(ctx).Namespace
}
}

View File

@ -0,0 +1,102 @@
/*
Copyright 2020 The Knative 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 v1
import (
"time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"knative.dev/pkg/apis"
)
// KRShaped is an interface for retrieving the duck elements of an arbitrary resource.
type KRShaped interface {
metav1.ObjectMetaAccessor
GetTypeMeta() *metav1.TypeMeta
GetStatus() *Status
GetConditionSet() apis.ConditionSet
}
// Asserts KResource conformance with KRShaped
var _ KRShaped = (*KResource)(nil)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// KResource is a skeleton type wrapping Conditions in the manner we expect
// resource writers defining compatible resources to embed it. We will
// typically use this type to deserialize Conditions ObjectReferences and
// access the Conditions data. This is not a real resource.
type KResource struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Status Status `json:"status"`
}
// Populate implements duck.Populatable
func (t *KResource) Populate() {
t.Status.ObservedGeneration = 42
t.Status.Conditions = Conditions{{
// Populate ALL fields
Type: "Birthday",
Status: corev1.ConditionTrue,
LastTransitionTime: apis.VolatileTime{Inner: metav1.NewTime(time.Date(1984, 02, 28, 18, 52, 00, 00, time.UTC))},
Reason: "Celebrate",
Message: "n3wScott, find your party hat :tada:",
}}
}
// GetListType implements apis.Listable
func (*KResource) GetListType() runtime.Object {
return &KResourceList{}
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// KResourceList is a list of KResource resources
type KResourceList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []KResource `json:"items"`
}
// GetTypeMeta retrieves the ObjectMeta of the KResource. Implements the KRShaped interface.
func (t *KResource) GetTypeMeta() *metav1.TypeMeta {
return &t.TypeMeta
}
// GetStatus retrieves the status of the KResource. Implements the KRShaped interface.
func (t *KResource) GetStatus() *Status {
return &t.Status
}
// GetConditionSet retrieves the condition set for this resource. Implements the KRShaped interface.
func (t *KResource) GetConditionSet() apis.ConditionSet {
// Note: KResources are unmarshalled from existing resources. This will only work properly for resources that
// have already been initialized to their type.
if cond := t.Status.GetCondition(apis.ConditionSucceeded); cond != nil {
return apis.NewBatchConditionSet()
}
return apis.NewLivingConditionSet()
}

View File

@ -0,0 +1,93 @@
/*
Copyright 2018 The Knative 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 v1
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"knative.dev/pkg/apis"
"knative.dev/pkg/apis/duck"
)
// +genduck
// PodSpecable is implemented by types containing a PodTemplateSpec
// in the manner of ReplicaSet, Deployment, DaemonSet, StatefulSet.
type PodSpecable corev1.PodTemplateSpec
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// WithPod is the shell that demonstrates how PodSpecable types wrap
// a PodSpec.
type WithPod struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec WithPodSpec `json:"spec,omitempty"`
}
// WithPodSpec is the shell around the PodSpecable within WithPod.
type WithPodSpec struct {
Template PodSpecable `json:"template,omitempty"`
}
// Assert that we implement the interfaces necessary to
// use duck.VerifyType.
var (
_ duck.Populatable = (*WithPod)(nil)
_ duck.Implementable = (*PodSpecable)(nil)
_ apis.Listable = (*WithPod)(nil)
)
// GetFullType implements duck.Implementable
func (*PodSpecable) GetFullType() duck.Populatable {
return &WithPod{}
}
// Populate implements duck.Populatable
func (t *WithPod) Populate() {
t.Spec.Template = PodSpecable{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "container-name",
Image: "container-image:latest",
}},
},
}
}
// GetListType implements apis.Listable
func (*WithPod) GetListType() runtime.Object {
return &WithPodList{}
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// WithPodList is a list of WithPod resources
type WithPodList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []WithPod `json:"items"`
}

View File

@ -0,0 +1,59 @@
/*
Copyright 2019 The Knative 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 v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"knative.dev/pkg/apis/duck"
)
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: duck.GroupName, Version: "v1"}
// Kind takes an unqualified kind and returns back a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
var (
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
AddToScheme = SchemeBuilder.AddToScheme
)
// Adds the list of known types to Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(
SchemeGroupVersion,
&KResource{},
(&KResource{}).GetListType(),
&AddressableType{},
(&AddressableType{}).GetListType(),
&Source{},
(&Source{}).GetListType(),
&WithPod{},
(&WithPod{}).GetListType(),
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}

View File

@ -0,0 +1,175 @@
/*
Copyright 2019 The Knative 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 v1
import (
"time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"knative.dev/pkg/apis"
"knative.dev/pkg/apis/duck"
)
// Source is an Implementable "duck type".
var _ duck.Implementable = (*Source)(nil)
// +genduck
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Source is the minimum resource shape to adhere to the Source Specification.
// This duck type is intended to allow implementors of Sources and
// Importers to verify their own resources meet the expectations.
// This is not a real resource.
// NOTE: The Source Specification is in progress and the shape and names could
// be modified until it has been accepted.
type Source struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec SourceSpec `json:"spec"`
Status SourceStatus `json:"status"`
}
type SourceSpec struct {
// Sink is a reference to an object that will resolve to a uri to use as the sink.
Sink Destination `json:"sink,omitempty"`
// CloudEventOverrides defines overrides to control the output format and
// modifications of the event sent to the sink.
// +optional
CloudEventOverrides *CloudEventOverrides `json:"ceOverrides,omitempty"`
}
// CloudEventOverrides defines arguments for a Source that control the output
// format of the CloudEvents produced by the Source.
type CloudEventOverrides struct {
// Extensions specify what attribute are added or overridden on the
// outbound event. Each `Extensions` key-value pair are set on the event as
// an attribute extension independently.
// +optional
Extensions map[string]string `json:"extensions,omitempty"`
}
// SourceStatus shows how we expect folks to embed Addressable in
// their Status field.
type SourceStatus struct {
// inherits duck/v1beta1 Status, which currently provides:
// * ObservedGeneration - the 'Generation' of the Service that was last
// processed by the controller.
// * Conditions - the latest available observations of a resource's current
// state.
Status `json:",inline"`
// SinkURI is the current active sink URI that has been configured for the
// Source.
// +optional
SinkURI *apis.URL `json:"sinkUri,omitempty"`
// CloudEventAttributes are the specific attributes that the Source uses
// as part of its CloudEvents.
// +optional
CloudEventAttributes []CloudEventAttributes `json:"ceAttributes,omitempty"`
}
// CloudEventAttributes specifies the attributes that a Source
// uses as part of its CloudEvents.
type CloudEventAttributes struct {
// Type refers to the CloudEvent type attribute.
Type string `json:"type,omitempty"`
// Source is the CloudEvents source attribute.
Source string `json:"source,omitempty"`
}
// IsReady returns true if the resource is ready overall.
func (ss *SourceStatus) IsReady() bool {
for _, c := range ss.Conditions {
switch c.Type {
// Look for the "happy" condition, which is the only condition that
// we can reliably understand to be the overall state of the resource.
case apis.ConditionReady, apis.ConditionSucceeded:
return c.IsTrue()
}
}
return false
}
var (
// Verify Source resources meet duck contracts.
_ duck.Populatable = (*Source)(nil)
_ apis.Listable = (*Source)(nil)
)
const (
// SourceConditionSinkProvided has status True when the Source
// has been configured with a sink target that is resolvable.
SourceConditionSinkProvided apis.ConditionType = "SinkProvided"
)
// GetFullType implements duck.Implementable
func (*Source) GetFullType() duck.Populatable {
return &Source{}
}
// Populate implements duck.Populatable
func (s *Source) Populate() {
s.Spec.Sink = Destination{
URI: &apis.URL{
Scheme: "https",
Host: "tableflip.dev",
RawQuery: "flip=mattmoor",
},
}
s.Spec.CloudEventOverrides = &CloudEventOverrides{
Extensions: map[string]string{"boosh": "kakow"},
}
s.Status.ObservedGeneration = 42
s.Status.Conditions = Conditions{{
// Populate ALL fields
Type: SourceConditionSinkProvided,
Status: corev1.ConditionTrue,
LastTransitionTime: apis.VolatileTime{Inner: metav1.NewTime(time.Date(1984, 02, 28, 18, 52, 00, 00, time.UTC))},
}}
s.Status.SinkURI = &apis.URL{
Scheme: "https",
Host: "tableflip.dev",
RawQuery: "flip=mattmoor",
}
s.Status.CloudEventAttributes = []CloudEventAttributes{{
Type: "dev.knative.foo",
Source: "http://knative.dev/knative/eventing",
}}
}
// GetListType implements apis.Listable
func (*Source) GetListType() runtime.Object {
return &SourceList{}
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// SourceList is a list of Source resources
type SourceList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []Source `json:"items"`
}

View File

@ -0,0 +1,110 @@
/*
Copyright 2019 The Knative 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 v1
import (
"context"
"knative.dev/pkg/apis"
"knative.dev/pkg/apis/duck"
)
// +genduck
// Conditions is a simple wrapper around apis.Conditions to implement duck.Implementable.
type Conditions apis.Conditions
// Conditions is an Implementable "duck type".
var _ duck.Implementable = (*Conditions)(nil)
// Status shows how we expect folks to embed Conditions in
// their Status field.
// WARNING: Adding fields to this struct will add them to all Knative resources.
type Status struct {
// ObservedGeneration is the 'Generation' of the Service that
// was last processed by the controller.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// Conditions the latest available observations of a resource's current state.
// +optional
// +patchMergeKey=type
// +patchStrategy=merge
Conditions Conditions `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}
var _ apis.ConditionsAccessor = (*Status)(nil)
// GetConditions implements apis.ConditionsAccessor
func (s *Status) GetConditions() apis.Conditions {
return apis.Conditions(s.Conditions)
}
// SetConditions implements apis.ConditionsAccessor
func (s *Status) SetConditions(c apis.Conditions) {
s.Conditions = Conditions(c)
}
// In order for Conditions to be Implementable, KResource must be Populatable.
var _ duck.Populatable = (*KResource)(nil)
// Ensure KResource satisfies apis.Listable
var _ apis.Listable = (*KResource)(nil)
// GetFullType implements duck.Implementable
func (*Conditions) GetFullType() duck.Populatable {
return &KResource{}
}
// GetCondition fetches the condition of the specified type.
func (s *Status) GetCondition(t apis.ConditionType) *apis.Condition {
for _, cond := range s.Conditions {
if cond.Type == t {
return &cond
}
}
return nil
}
// ConvertTo helps implement apis.Convertible for types embedding this Status.
//
// By default apis.ConditionReady and apis.ConditionSucceeded will be copied over to the
// sink. Other conditions types are tested against a list of predicates. If any of the predicates
// return true the condition type will be copied to the sink
func (source *Status) ConvertTo(ctx context.Context, sink *Status, predicates ...func(apis.ConditionType) bool) {
sink.ObservedGeneration = source.ObservedGeneration
conditions := make(apis.Conditions, 0, len(source.Conditions))
for _, c := range source.Conditions {
// Copy over the "happy" condition, which is the only condition that
// we can reliably transfer.
if c.Type == apis.ConditionReady || c.Type == apis.ConditionSucceeded {
conditions = append(conditions, c)
continue
}
for _, predicate := range predicates {
if predicate(c.Type) {
conditions = append(conditions, c)
break
}
}
}
sink.SetConditions(conditions)
}

View File

@ -0,0 +1,519 @@
// +build !ignore_autogenerated
/*
Copyright 2020 The Knative 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
apis "knative.dev/pkg/apis"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AddressStatus) DeepCopyInto(out *AddressStatus) {
*out = *in
if in.Address != nil {
in, out := &in.Address, &out.Address
*out = new(Addressable)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddressStatus.
func (in *AddressStatus) DeepCopy() *AddressStatus {
if in == nil {
return nil
}
out := new(AddressStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Addressable) DeepCopyInto(out *Addressable) {
*out = *in
if in.URL != nil {
in, out := &in.URL, &out.URL
*out = new(apis.URL)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Addressable.
func (in *Addressable) DeepCopy() *Addressable {
if in == nil {
return nil
}
out := new(Addressable)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AddressableType) DeepCopyInto(out *AddressableType) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddressableType.
func (in *AddressableType) DeepCopy() *AddressableType {
if in == nil {
return nil
}
out := new(AddressableType)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *AddressableType) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AddressableTypeList) DeepCopyInto(out *AddressableTypeList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]AddressableType, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddressableTypeList.
func (in *AddressableTypeList) DeepCopy() *AddressableTypeList {
if in == nil {
return nil
}
out := new(AddressableTypeList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *AddressableTypeList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CloudEventAttributes) DeepCopyInto(out *CloudEventAttributes) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CloudEventAttributes.
func (in *CloudEventAttributes) DeepCopy() *CloudEventAttributes {
if in == nil {
return nil
}
out := new(CloudEventAttributes)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CloudEventOverrides) DeepCopyInto(out *CloudEventOverrides) {
*out = *in
if in.Extensions != nil {
in, out := &in.Extensions, &out.Extensions
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CloudEventOverrides.
func (in *CloudEventOverrides) DeepCopy() *CloudEventOverrides {
if in == nil {
return nil
}
out := new(CloudEventOverrides)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in Conditions) DeepCopyInto(out *Conditions) {
{
in := &in
*out = make(Conditions, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
return
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Conditions.
func (in Conditions) DeepCopy() Conditions {
if in == nil {
return nil
}
out := new(Conditions)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Destination) DeepCopyInto(out *Destination) {
*out = *in
if in.Ref != nil {
in, out := &in.Ref, &out.Ref
*out = new(KReference)
**out = **in
}
if in.URI != nil {
in, out := &in.URI, &out.URI
*out = new(apis.URL)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Destination.
func (in *Destination) DeepCopy() *Destination {
if in == nil {
return nil
}
out := new(Destination)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KReference) DeepCopyInto(out *KReference) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KReference.
func (in *KReference) DeepCopy() *KReference {
if in == nil {
return nil
}
out := new(KReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KResource) DeepCopyInto(out *KResource) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KResource.
func (in *KResource) DeepCopy() *KResource {
if in == nil {
return nil
}
out := new(KResource)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *KResource) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KResourceList) DeepCopyInto(out *KResourceList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]KResource, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KResourceList.
func (in *KResourceList) DeepCopy() *KResourceList {
if in == nil {
return nil
}
out := new(KResourceList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *KResourceList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodSpecable) DeepCopyInto(out *PodSpecable) {
*out = *in
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSpecable.
func (in *PodSpecable) DeepCopy() *PodSpecable {
if in == nil {
return nil
}
out := new(PodSpecable)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Source) DeepCopyInto(out *Source) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Source.
func (in *Source) DeepCopy() *Source {
if in == nil {
return nil
}
out := new(Source)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Source) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SourceList) DeepCopyInto(out *SourceList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Source, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceList.
func (in *SourceList) DeepCopy() *SourceList {
if in == nil {
return nil
}
out := new(SourceList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *SourceList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SourceSpec) DeepCopyInto(out *SourceSpec) {
*out = *in
in.Sink.DeepCopyInto(&out.Sink)
if in.CloudEventOverrides != nil {
in, out := &in.CloudEventOverrides, &out.CloudEventOverrides
*out = new(CloudEventOverrides)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceSpec.
func (in *SourceSpec) DeepCopy() *SourceSpec {
if in == nil {
return nil
}
out := new(SourceSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SourceStatus) DeepCopyInto(out *SourceStatus) {
*out = *in
in.Status.DeepCopyInto(&out.Status)
if in.SinkURI != nil {
in, out := &in.SinkURI, &out.SinkURI
*out = new(apis.URL)
(*in).DeepCopyInto(*out)
}
if in.CloudEventAttributes != nil {
in, out := &in.CloudEventAttributes, &out.CloudEventAttributes
*out = make([]CloudEventAttributes, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceStatus.
func (in *SourceStatus) DeepCopy() *SourceStatus {
if in == nil {
return nil
}
out := new(SourceStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Status) DeepCopyInto(out *Status) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make(Conditions, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Status.
func (in *Status) DeepCopy() *Status {
if in == nil {
return nil
}
out := new(Status)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WithPod) DeepCopyInto(out *WithPod) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WithPod.
func (in *WithPod) DeepCopy() *WithPod {
if in == nil {
return nil
}
out := new(WithPod)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *WithPod) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WithPodList) DeepCopyInto(out *WithPodList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]WithPod, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WithPodList.
func (in *WithPodList) DeepCopy() *WithPodList {
if in == nil {
return nil
}
out := new(WithPodList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *WithPodList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WithPodSpec) DeepCopyInto(out *WithPodSpec) {
*out = *in
in.Template.DeepCopyInto(&out.Template)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WithPodSpec.
func (in *WithPodSpec) DeepCopy() *WithPodSpec {
if in == nil {
return nil
}
out := new(WithPodSpec)
in.DeepCopyInto(out)
return out
}

View File

@ -0,0 +1,113 @@
/*
Copyright 2018 The Knative 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 duck
import (
"encoding/json"
"fmt"
"knative.dev/pkg/apis"
"knative.dev/pkg/kmp"
)
// Implementable is implemented by the Fooable duck type that consumers
// are expected to embed as a `.status.fooable` field.
type Implementable interface {
// GetFullType returns an instance of a full resource wrapping
// an instance of this Implementable that can populate its fields
// to verify json roundtripping.
GetFullType() Populatable
}
// Populatable is implemented by a skeleton resource wrapping an Implementable
// duck type. It will generally have TypeMeta, ObjectMeta, and a Status field
// wrapping a Fooable field.
type Populatable interface {
apis.Listable
// Populate fills in all possible fields, so that we can verify that
// they roundtrip properly through JSON.
Populate()
}
// VerifyType verifies that a particular concrete resource properly implements
// the provided Implementable duck type. It is expected that under the resource
// definition implementing a particular "Fooable" that one would write:
//
// type ConcreteResource struct { ... }
//
// // Check that ConcreteResource properly implement Fooable.
// err := duck.VerifyType(&ConcreteResource{}, &something.Fooable{})
//
// This will return an error if the duck typing is not satisfied.
func VerifyType(instance interface{}, iface Implementable) error {
// Create instances of the full resource for our input and ultimate result
// that we will compare at the end.
input, output := iface.GetFullType(), iface.GetFullType()
if err := roundTrip(instance, input, output); err != nil {
return err
}
// Now verify that we were able to roundtrip all of our fields through the type
// we are checking.
if diff, err := kmp.SafeDiff(input, output); err != nil {
return err
} else if diff != "" {
return fmt.Errorf("%T does not implement the duck type %T, the following fields were lost: %s",
instance, iface, diff)
}
return nil
}
// ConformsToType will return true or false depending on whether a
// concrete resource properly implements the provided Implementable
// duck type.
//
// It will return an error if marshal/unmarshalling fails
func ConformsToType(instance interface{}, iface Implementable) (bool, error) {
input, output := iface.GetFullType(), iface.GetFullType()
if err := roundTrip(instance, input, output); err != nil {
return false, err
}
return kmp.SafeEqual(input, output)
}
func roundTrip(instance interface{}, input, output Populatable) error {
// Populate our input resource with values we will roundtrip.
input.Populate()
// Serialize the input to JSON and deserialize that into the provided instance
// of the type that we are checking.
if before, err := json.Marshal(input); err != nil {
return fmt.Errorf("error serializing duck type %T error: %s", input, err)
} else if err := json.Unmarshal(before, instance); err != nil {
return fmt.Errorf("error deserializing duck type %T into %T error: %s", input, instance, err)
}
// Serialize the instance we are checking to JSON and deserialize that into the
// output resource.
if after, err := json.Marshal(instance); err != nil {
return fmt.Errorf("error serializing %T error: %s", instance, err)
} else if err := json.Unmarshal(after, output); err != nil {
return fmt.Errorf("error deserializing %T into duck type %T error: %s", instance, output, err)
}
return nil
}

View File

@ -0,0 +1,59 @@
/*
Copyright 2020 The Knative 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 logging
import (
"strings"
"go.uber.org/zap/zapcore"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
)
// This file contains the specific object encoders for use in Knative
// to optimize logging experience and performance.
// StringSet returns a marshaler for the set of strings.
// To use this in sugared logger do:
// logger.Infow("Revision State", zap.Object("healthy", logging.StringSet(healthySet)),
// zap.Object("unhealthy", logging.StringSet(unhealthySet)))
// To use with non-sugared logger do:
// logger.Info("Revision State", zap.Object("healthy", logging.StringSet(healthySet)),
// zap.Object("unhealthy", logging.StringSet(unhealthySet)))
func StringSet(s sets.String) zapcore.ObjectMarshalerFunc {
return func(enc zapcore.ObjectEncoder) error {
enc.AddString("keys", strings.Join(s.UnsortedList(), ","))
return nil
}
}
// NamespacedName returns a marshaler for NamespacedName.
// To use this in sugared logger do:
// logger.Infow("Enqueuing", zap.Object("key", logging.NamespacedName(n)))
// To use with non-sugared logger do:
// logger.Info("Enqueuing", zap.Object("key", logging.NamespacedName(n)))
func NamespacedName(n types.NamespacedName) zapcore.ObjectMarshalerFunc {
return func(enc zapcore.ObjectEncoder) error {
if n.Namespace != "" {
enc.AddString("key", n.Name)
} else {
enc.AddString("key", n.Namespace+"/"+n.Name)
}
return nil
}
}

View File

@ -0,0 +1,55 @@
/*
Copyright 2020 The Knative 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
https://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 reconciler
import (
"context"
duckv1 "knative.dev/pkg/apis/duck/v1"
"knative.dev/pkg/logging"
)
const failedGenerationBump = "NewObservedGenFailure"
// PreProcessReconcile contains logic to apply before reconciliation of a resource.
func PreProcessReconcile(ctx context.Context, resource duckv1.KRShaped) {
newStatus := resource.GetStatus()
if newStatus.ObservedGeneration != resource.GetObjectMeta().GetGeneration() {
condSet := resource.GetConditionSet()
manager := condSet.Manage(newStatus)
// Reset Ready/Successful to unknown. The reconciler is expected to overwrite this.
manager.MarkUnknown(condSet.GetTopLevelConditionType(), failedGenerationBump, "unsuccessfully observed a new generation")
}
}
// PostProcessReconcile contains logic to apply after reconciliation of a resource.
func PostProcessReconcile(ctx context.Context, resource duckv1.KRShaped) {
logger := logging.FromContext(ctx)
newStatus := resource.GetStatus()
mgr := resource.GetConditionSet().Manage(newStatus)
// Bump observed generation to denote that we have processed this
// generation regardless of success or failure.
newStatus.ObservedGeneration = resource.GetObjectMeta().GetGeneration()
rc := mgr.GetTopLevelCondition()
if rc.Reason == failedGenerationBump {
logger.Warn("A reconciler observed a new generation without updating the resource status")
}
}

23
vendor/knative.dev/pkg/tracker/doc.go vendored Normal file
View File

@ -0,0 +1,23 @@
/*
Copyright 2018 The Knative 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.
*/
// +k8s:deepcopy-gen=package
// Package tracker defines a utility to enable Reconcilers to trigger
// reconciliations when objects that are cross-referenced change, so
// that the level-based reconciliation can react to the change. The
// prototypical cross-reference in Kubernetes is corev1.ObjectReference.
package tracker

View File

@ -0,0 +1,306 @@
/*
Copyright 2018 The Knative 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 tracker
import (
"fmt"
"sort"
"strings"
"sync"
"time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation"
"knative.dev/pkg/kmeta"
)
// New returns an implementation of Interface that lets a Reconciler
// register a particular resource as watching an ObjectReference for
// a particular lease duration. This watch must be refreshed
// periodically (e.g. by a controller resync) or it will expire.
//
// When OnChanged is called by the informer for a particular
// GroupVersionKind, the provided callback is called with the "key"
// of each object actively watching the changed object.
func New(callback func(types.NamespacedName), lease time.Duration) Interface {
return &impl{
leaseDuration: lease,
cb: callback,
}
}
type impl struct {
m sync.Mutex
// exact maps from an object reference to the set of
// keys for objects watching it.
exact map[Reference]set
// inexact maps from a partial object reference (no name/selector) to
// a map from watcher keys to the compiled selector and expiry.
inexact map[Reference]matchers
// The amount of time that an object may watch another
// before having to renew the lease.
leaseDuration time.Duration
cb func(types.NamespacedName)
}
// Check that impl implements Interface.
var _ Interface = (*impl)(nil)
// set is a map from keys to expirations
type set map[types.NamespacedName]time.Time
// matchers maps the tracker's key to the matcher.
type matchers map[types.NamespacedName]matcher
// matcher holds the selector and expiry for matching tracked objects.
type matcher struct {
// The selector to complete the match.
selector labels.Selector
// When this lease expires.
expiry time.Time
}
// Track implements Interface.
func (i *impl) Track(ref corev1.ObjectReference, obj interface{}) error {
return i.TrackReference(Reference{
APIVersion: ref.APIVersion,
Kind: ref.Kind,
Namespace: ref.Namespace,
Name: ref.Name,
}, obj)
}
func (i *impl) TrackReference(ref Reference, obj interface{}) error {
invalidFields := map[string][]string{
"APIVersion": validation.IsQualifiedName(ref.APIVersion),
"Kind": validation.IsCIdentifier(ref.Kind),
"Namespace": validation.IsDNS1123Label(ref.Namespace),
}
var selector labels.Selector
fieldErrors := []string{}
switch {
case ref.Selector != nil && ref.Name != "":
fieldErrors = append(fieldErrors, "cannot provide both Name and Selector")
case ref.Name != "":
invalidFields["Name"] = validation.IsDNS1123Subdomain(ref.Name)
case ref.Selector != nil:
ls, err := metav1.LabelSelectorAsSelector(ref.Selector)
if err != nil {
invalidFields["Selector"] = []string{err.Error()}
}
selector = ls
default:
fieldErrors = append(fieldErrors, "must provide either Name or Selector")
}
for k, v := range invalidFields {
for _, msg := range v {
fieldErrors = append(fieldErrors, fmt.Sprintf("%s: %s", k, msg))
}
}
if len(fieldErrors) > 0 {
sort.Strings(fieldErrors)
return fmt.Errorf("invalid Reference:\n%s", strings.Join(fieldErrors, "\n"))
}
// Determine the key of the object tracking this reference.
object, err := kmeta.DeletionHandlingAccessor(obj)
if err != nil {
return err
}
key := types.NamespacedName{Namespace: object.GetNamespace(), Name: object.GetName()}
i.m.Lock()
// Call the callback without the lock held.
var keys []types.NamespacedName
defer func(cb func(types.NamespacedName)) {
for _, key := range keys {
cb(key)
}
}(i.cb) // read i.cb with the lock held
defer i.m.Unlock()
if i.exact == nil {
i.exact = make(map[Reference]set)
}
if i.inexact == nil {
i.inexact = make(map[Reference]matchers)
}
// If the reference uses Name then it is an exact match.
if selector == nil {
l, ok := i.exact[ref]
if !ok {
l = set{}
}
if expiry, ok := l[key]; !ok || isExpired(expiry) {
// When covering an uncovered key, immediately call the
// registered callback to ensure that the following pattern
// doesn't create problems:
// foo, err := lister.Get(key)
// // Later...
// err := tracker.TrackReference(fooRef, parent)
// In this example, "Later" represents a window where "foo" may
// have changed or been created while the Track is not active.
// The simplest way of eliminating such a window is to call the
// callback to "catch up" immediately following new
// registrations.
keys = append(keys, key)
}
// Overwrite the key with a new expiration.
l[key] = time.Now().Add(i.leaseDuration)
i.exact[ref] = l
return nil
}
// Otherwise, it is an inexact match by selector.
partialRef := Reference{
APIVersion: ref.APIVersion,
Kind: ref.Kind,
Namespace: ref.Namespace,
// Exclude the selector.
}
l, ok := i.inexact[partialRef]
if !ok {
l = matchers{}
}
if m, ok := l[key]; !ok || isExpired(m.expiry) {
// When covering an uncovered key, immediately call the
// registered callback to ensure that the following pattern
// doesn't create problems:
// foo, err := lister.Get(key)
// // Later...
// err := tracker.TrackReference(fooRef, parent)
// In this example, "Later" represents a window where "foo" may
// have changed or been created while the Track is not active.
// The simplest way of eliminating such a window is to call the
// callback to "catch up" immediately following new
// registrations.
keys = append(keys, key)
}
// Overwrite the key with a new expiration.
l[key] = matcher{
selector: selector,
expiry: time.Now().Add(i.leaseDuration),
}
i.inexact[partialRef] = l
return nil
}
func isExpired(expiry time.Time) bool {
return time.Now().After(expiry)
}
// OnChanged implements Interface.
func (i *impl) OnChanged(obj interface{}) {
item, err := kmeta.DeletionHandlingAccessor(obj)
if err != nil {
return
}
or := kmeta.ObjectReference(item)
ref := Reference{
APIVersion: or.APIVersion,
Kind: or.Kind,
Namespace: or.Namespace,
Name: or.Name,
}
i.m.Lock()
// Call the callbacks without the lock held.
var keys []types.NamespacedName
defer func(cb func(types.NamespacedName)) {
for _, key := range keys {
cb(key)
}
}(i.cb) // read i.cb with the lock held
defer i.m.Unlock()
// Handle exact matches.
s, ok := i.exact[ref]
if ok {
for key, expiry := range s {
// If the expiration has lapsed, then delete the key.
if isExpired(expiry) {
delete(s, key)
continue
}
keys = append(keys, key)
}
if len(s) == 0 {
delete(i.exact, ref)
}
}
// Handle inexact matches.
ref.Name = ""
ms, ok := i.inexact[ref]
if ok {
ls := labels.Set(item.GetLabels())
for key, m := range ms {
// If the expiration has lapsed, then delete the key.
if isExpired(m.expiry) {
delete(ms, key)
continue
}
if m.selector.Matches(ls) {
keys = append(keys, key)
}
}
if len(s) == 0 {
delete(i.exact, ref)
}
}
}
// OnChanged implements Interface.
func (i *impl) OnDeletedObserver(obj interface{}) {
item, err := kmeta.DeletionHandlingAccessor(obj)
if err != nil {
return
}
key := types.NamespacedName{Namespace: item.GetNamespace(), Name: item.GetName()}
i.m.Lock()
defer i.m.Unlock()
// Remove exact matches.
for ref, matchers := range i.exact {
delete(matchers, key)
if len(matchers) == 0 {
delete(i.exact, ref)
}
}
// Remove inexact matches.
for ref, matchers := range i.inexact {
delete(matchers, key)
if len(matchers) == 0 {
delete(i.exact, ref)
}
}
}

View File

@ -0,0 +1,175 @@
/*
Copyright 2018 The Knative 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 tracker
import (
"context"
"strings"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation"
"knative.dev/pkg/apis"
)
// Reference is modeled after corev1.ObjectReference, but omits fields
// unsupported by the tracker, and permits us to extend things in
// divergent ways.
type Reference struct {
// API version of the referent.
// +optional
APIVersion string `json:"apiVersion,omitempty"`
// Kind of the referent.
// +optional
Kind string `json:"kind,omitempty"`
// Namespace of the referent.
// +optional
Namespace string `json:"namespace,omitempty"`
// Name of the referent.
// Mutually exclusive with Selector.
// +optional
Name string `json:"name,omitempty"`
// Selector of the referents.
// Mutually exclusive with Name.
// +optional
Selector *metav1.LabelSelector `json:"selector,omitempty"`
}
// Interface defines the interface through which an object can register
// that it is tracking another object by reference.
type Interface interface {
// Track tells us that "obj" is tracking changes to the
// referenced object.
// DEPRECATED: use TrackReference
Track(ref corev1.ObjectReference, obj interface{}) error
// Track tells us that "obj" is tracking changes to the
// referenced object.
TrackReference(ref Reference, obj interface{}) error
// OnChanged is a callback to register with the InformerFactory
// so that we are notified for appropriate object changes.
OnChanged(obj interface{})
// OnDeletedObserver is a callback to register with the InformerFactory
// so that we are notified for deletions of a watching parent to
// remove the respective tracking.
OnDeletedObserver(obj interface{})
}
// GroupVersionKind returns the GroupVersion of the object referenced.
func (ref *Reference) GroupVersionKind() schema.GroupVersionKind {
gv, _ := schema.ParseGroupVersion(ref.APIVersion)
return schema.GroupVersionKind{
Group: gv.Group,
Version: gv.Version,
Kind: ref.Kind,
}
}
// ObjectReference returns the tracker Reference as an ObjectReference.
func (ref *Reference) ObjectReference() corev1.ObjectReference {
return corev1.ObjectReference{
APIVersion: ref.APIVersion,
Kind: ref.Kind,
Namespace: ref.Namespace,
Name: ref.Name,
}
}
// ValidateObjectReference validates that the Reference uses a subset suitable for
// translation to a corev1.ObjectReference. This helper is intended to simplify
// validating a particular (narrow) use of tracker.Reference.
func (ref *Reference) ValidateObjectReference(ctx context.Context) *apis.FieldError {
var errs *apis.FieldError
// Required fields
if ref.APIVersion == "" {
errs = errs.Also(apis.ErrMissingField("apiVersion"))
} else if verrs := validation.IsQualifiedName(ref.APIVersion); len(verrs) != 0 {
errs = errs.Also(apis.ErrInvalidValue(strings.Join(verrs, ", "), "apiVersion"))
}
if ref.Kind == "" {
errs = errs.Also(apis.ErrMissingField("kind"))
} else if verrs := validation.IsCIdentifier(ref.Kind); len(verrs) != 0 {
errs = errs.Also(apis.ErrInvalidValue(strings.Join(verrs, ", "), "kind"))
}
if ref.Name == "" {
errs = errs.Also(apis.ErrMissingField("name"))
} else if verrs := validation.IsDNS1123Label(ref.Name); len(verrs) != 0 {
errs = errs.Also(apis.ErrInvalidValue(strings.Join(verrs, ", "), "name"))
}
if ref.Namespace == "" {
errs = errs.Also(apis.ErrMissingField("namespace"))
} else if verrs := validation.IsDNS1123Label(ref.Namespace); len(verrs) != 0 {
errs = errs.Also(apis.ErrInvalidValue(strings.Join(verrs, ", "), "namespace"))
}
// Disallowed fields in ObjectReference-compatible context.
if ref.Selector != nil {
errs = errs.Also(apis.ErrDisallowedFields("selector"))
}
return errs
}
func (ref *Reference) Validate(ctx context.Context) *apis.FieldError {
var errs *apis.FieldError
// Required fields
if ref.APIVersion == "" {
errs = errs.Also(apis.ErrMissingField("apiVersion"))
} else if verrs := validation.IsQualifiedName(ref.APIVersion); len(verrs) != 0 {
errs = errs.Also(apis.ErrInvalidValue(strings.Join(verrs, ", "), "apiVersion"))
}
if ref.Kind == "" {
errs = errs.Also(apis.ErrMissingField("kind"))
} else if verrs := validation.IsCIdentifier(ref.Kind); len(verrs) != 0 {
errs = errs.Also(apis.ErrInvalidValue(strings.Join(verrs, ", "), "kind"))
}
if ref.Namespace == "" {
errs = errs.Also(apis.ErrMissingField("namespace"))
} else if verrs := validation.IsDNS1123Label(ref.Namespace); len(verrs) != 0 {
errs = errs.Also(apis.ErrInvalidValue(strings.Join(verrs, ", "), "namespace"))
}
switch {
case ref.Selector != nil && ref.Name != "":
errs = errs.Also(apis.ErrMultipleOneOf("selector", "name"))
case ref.Selector != nil:
_, err := metav1.LabelSelectorAsSelector(ref.Selector)
if err != nil {
errs = errs.Also(apis.ErrInvalidValue(err.Error(), "selector"))
}
case ref.Name != "":
if verrs := validation.IsDNS1123Label(ref.Name); len(verrs) != 0 {
errs = errs.Also(apis.ErrInvalidValue(strings.Join(verrs, ", "), "name"))
}
default:
errs = errs.Also(apis.ErrMissingOneOf("selector", "name"))
}
return errs
}

View File

@ -0,0 +1,46 @@
// +build !ignore_autogenerated
/*
Copyright 2020 The Knative 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package tracker
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Reference) DeepCopyInto(out *Reference) {
*out = *in
if in.Selector != nil {
in, out := &in.Selector, &out.Selector
*out = new(v1.LabelSelector)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Reference.
func (in *Reference) DeepCopy() *Reference {
if in == nil {
return nil
}
out := new(Reference)
in.DeepCopyInto(out)
return out
}

10
vendor/modules.txt vendored
View File

@ -244,6 +244,8 @@ golang.org/x/tools/internal/imports
# golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
golang.org/x/xerrors
golang.org/x/xerrors/internal
# gomodules.xyz/jsonpatch/v2 v2.0.1
gomodules.xyz/jsonpatch/v2
# gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485
gonum.org/v1/gonum/blas
gonum.org/v1/gonum/blas/blas64
@ -444,6 +446,7 @@ k8s.io/apimachinery/third_party/forked/golang/reflect
# k8s.io/client-go v11.0.1-0.20190805182717-6502b5e7b1b5+incompatible => k8s.io/client-go v0.16.4
k8s.io/client-go/discovery
k8s.io/client-go/discovery/fake
k8s.io/client-go/dynamic
k8s.io/client-go/informers
k8s.io/client-go/informers/admissionregistration
k8s.io/client-go/informers/admissionregistration/v1
@ -653,8 +656,10 @@ k8s.io/kube-openapi/pkg/util/sets
k8s.io/utils/buffer
k8s.io/utils/integer
k8s.io/utils/trace
# knative.dev/pkg v0.0.0-20200506001744-478962f05e2b
# knative.dev/pkg v0.0.0-20200507011344-2581370e4a37
knative.dev/pkg/apis
knative.dev/pkg/apis/duck
knative.dev/pkg/apis/duck/v1
knative.dev/pkg/changeset
knative.dev/pkg/codegen/cmd/injection-gen
knative.dev/pkg/codegen/cmd/injection-gen/args
@ -670,7 +675,8 @@ knative.dev/pkg/logging/logkey
knative.dev/pkg/metrics
knative.dev/pkg/metrics/metricskey
knative.dev/pkg/reconciler
# knative.dev/test-infra v0.0.0-20200506045344-e71b1288c15c
knative.dev/pkg/tracker
# knative.dev/test-infra v0.0.0-20200506231144-c8dd15bb7f0b
knative.dev/test-infra/scripts
knative.dev/test-infra/tools/dep-collector
# sigs.k8s.io/yaml v1.1.0