mirror of https://github.com/knative/caching.git
[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:
parent
ca448db68d
commit
f613680e15
4
go.mod
4
go.mod
|
@ -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
13
go.sum
|
@ -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=
|
||||
|
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
)
|
|
@ -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=
|
|
@ -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{}
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -0,0 +1,325 @@
|
|||
# Knative Duck Typing
|
||||
|
||||

|
||||
|
||||
**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 Knative’s
|
||||
[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 resource’s 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
|
||||
```
|
|
@ -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
|
||||
```
|
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
)
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
)
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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"`
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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"`
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"`
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue