Upgrade k8s package verison (#5358)

* upgrade k8s package version

Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>

* Script upgrade and code formatting.

Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>

Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>
This commit is contained in:
hongzhouzi
2022-11-15 14:56:38 +08:00
committed by GitHub
parent 5f91c1663a
commit 44167aa47a
3106 changed files with 321340 additions and 172080 deletions

View File

@@ -22,6 +22,7 @@ import (
"io"
"net/url"
"os"
"path/filepath"
"strings"
"sync"
@@ -38,13 +39,14 @@ import (
"k8s.io/client-go/discovery"
"k8s.io/client-go/rest"
"k8s.io/client-go/restmapper"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/kyaml/filesys"
)
var FileExtensions = []string{".json", ".yaml", ".yml"}
var InputExtensions = append(FileExtensions, "stdin")
const defaultHttpGetAttempts int = 3
const defaultHttpGetAttempts = 3
const pathNotExistError = "the path %q does not exist"
// Builder provides convenience functions for taking arguments and parameters
// from the command line and converting them to a list of resources to iterate
@@ -72,9 +74,10 @@ type Builder struct {
errs []error
paths []Visitor
stream bool
dir bool
paths []Visitor
stream bool
stdinInUse bool
dir bool
labelSelector *string
fieldSelector *string
@@ -82,7 +85,8 @@ type Builder struct {
limitChunks int64
requestTransforms []RequestTransform
resources []string
resources []string
subresource string
namespace string
allNamespace bool
@@ -121,6 +125,8 @@ Example resource specifications include:
'-f rsrc.yaml'
'--filename=rsrc.json'`)
var StdinMultiUseError = errors.New("standard input cannot be used for multiple arguments")
// TODO: expand this to include other errors.
func IsUsageError(err error) bool {
if err == nil {
@@ -209,7 +215,7 @@ func NewBuilder(restClientGetter RESTClientGetter) *Builder {
return newBuilder(
restClientGetter.ToRESTConfig,
(&cachingRESTMapperFunc{delegate: restClientGetter.ToRESTMapper}).ToRESTMapper,
restClientGetter.ToRESTMapper,
(&cachingCategoryExpanderFunc{delegate: categoryExpanderFn}).ToCategoryExpander,
)
}
@@ -252,10 +258,15 @@ func (b *Builder) FilenameParam(enforceNamespace bool, filenameOptions *Filename
}
b.URL(defaultHttpGetAttempts, url)
default:
if !recursive {
matches, err := expandIfFilePattern(s)
if err != nil {
b.errs = append(b.errs, err)
continue
}
if !recursive && len(matches) == 1 {
b.singleItemImplied = true
}
b.Path(recursive, s)
b.Path(recursive, matches...)
}
}
if filenameOptions.Kustomize != "" {
@@ -362,13 +373,31 @@ func (b *Builder) URL(httpAttemptCount int, urls ...*url.URL) *Builder {
// Stdin will read objects from the standard input. If ContinueOnError() is set
// prior to this method being called, objects in the stream that are unrecognized
// will be ignored (but logged at V(2)).
// will be ignored (but logged at V(2)). If StdinInUse() is set prior to this method
// being called, an error will be recorded as there are multiple entities trying to use
// the single standard input stream.
func (b *Builder) Stdin() *Builder {
b.stream = true
if b.stdinInUse {
b.errs = append(b.errs, StdinMultiUseError)
}
b.stdinInUse = true
b.paths = append(b.paths, FileVisitorForSTDIN(b.mapper, b.schema))
return b
}
// StdinInUse will mark standard input as in use by this Builder, and therefore standard
// input should not be used by another entity. If Stdin() is set prior to this method
// being called, an error will be recorded as there are multiple entities trying to use
// the single standard input stream.
func (b *Builder) StdinInUse() *Builder {
if b.stdinInUse {
b.errs = append(b.errs, StdinMultiUseError)
}
b.stdinInUse = true
return b
}
// Stream will read objects from the provided reader, and if an error occurs will
// include the name string in the error message. If ContinueOnError() is set
// prior to this method being called, objects in the stream that are unrecognized
@@ -388,7 +417,7 @@ func (b *Builder) Path(recursive bool, paths ...string) *Builder {
for _, p := range paths {
_, err := os.Stat(p)
if os.IsNotExist(err) {
b.errs = append(b.errs, fmt.Errorf("the path %q does not exist", p))
b.errs = append(b.errs, fmt.Errorf(pathNotExistError, p))
continue
}
if err != nil {
@@ -534,6 +563,13 @@ func (b *Builder) TransformRequests(opts ...RequestTransform) *Builder {
return b
}
// Subresource instructs the builder to retrieve the object at the
// subresource path instead of the main resource path.
func (b *Builder) Subresource(subresource string) *Builder {
b.subresource = subresource
return b
}
// SelectEverythingParam
func (b *Builder) SelectAllParam(selectAll bool) *Builder {
if selectAll && (b.labelSelector != nil || b.fieldSelector != nil) {
@@ -865,6 +901,10 @@ func (b *Builder) visitBySelector() *Result {
if len(b.resources) == 0 {
return result.withError(fmt.Errorf("at least one resource must be specified to use a selector"))
}
if len(b.subresource) != 0 {
return result.withError(fmt.Errorf("subresource cannot be used when bulk resources are specified"))
}
mappings, err := b.resourceMappings()
if err != nil {
result.err = err
@@ -911,9 +951,9 @@ func (b *Builder) getClient(gv schema.GroupVersion) (RESTClient, error) {
case b.fakeClientFn != nil:
client, err = b.fakeClientFn(gv)
case b.negotiatedSerializer != nil:
client, err = b.clientConfigFn.clientForGroupVersion(gv, b.negotiatedSerializer)
client, err = b.clientConfigFn.withStdinUnavailable(b.stdinInUse).clientForGroupVersion(gv, b.negotiatedSerializer)
default:
client, err = b.clientConfigFn.unstructuredClientForGroupVersion(gv)
client, err = b.clientConfigFn.withStdinUnavailable(b.stdinInUse).unstructuredClientForGroupVersion(gv)
}
if err != nil {
@@ -986,10 +1026,11 @@ func (b *Builder) visitByResource() *Result {
}
info := &Info{
Client: client,
Mapping: mapping,
Namespace: selectorNamespace,
Name: tuple.Name,
Client: client,
Mapping: mapping,
Namespace: selectorNamespace,
Name: tuple.Name,
Subresource: b.subresource,
}
items = append(items, info)
}
@@ -1050,10 +1091,11 @@ func (b *Builder) visitByName() *Result {
visitors := []Visitor{}
for _, name := range b.names {
info := &Info{
Client: client,
Mapping: mapping,
Namespace: selectorNamespace,
Name: name,
Client: client,
Mapping: mapping,
Namespace: selectorNamespace,
Name: name,
Subresource: b.subresource,
}
visitors = append(visitors, info)
}
@@ -1134,10 +1176,9 @@ func (b *Builder) Do() *Result {
helpers = append(helpers, RetrieveLazy)
}
if b.continueOnError {
r.visitor = NewDecoratedVisitor(ContinueOnErrorVisitor{r.visitor}, helpers...)
} else {
r.visitor = NewDecoratedVisitor(r.visitor, helpers...)
r.visitor = ContinueOnErrorVisitor{Visitor: r.visitor}
}
r.visitor = NewDecoratedVisitor(r.visitor, helpers...)
return r
}
@@ -1166,26 +1207,21 @@ func HasNames(args []string) (bool, error) {
return hasCombinedTypes || len(args) > 1, nil
}
type cachingRESTMapperFunc struct {
delegate RESTMapperFunc
lock sync.Mutex
cached meta.RESTMapper
}
func (c *cachingRESTMapperFunc) ToRESTMapper() (meta.RESTMapper, error) {
c.lock.Lock()
defer c.lock.Unlock()
if c.cached != nil {
return c.cached, nil
// expandIfFilePattern returns all the filenames that match the input pattern
// or the filename if it is a specific filename and not a pattern.
// If the input is a pattern and it yields no result it will result in an error.
func expandIfFilePattern(pattern string) ([]string, error) {
if _, err := os.Stat(pattern); os.IsNotExist(err) {
matches, err := filepath.Glob(pattern)
if err == nil && len(matches) == 0 {
return nil, fmt.Errorf(pathNotExistError, pattern)
}
if err == filepath.ErrBadPattern {
return nil, fmt.Errorf("pattern %q is not valid: %v", pattern, err)
}
return matches, err
}
ret, err := c.delegate()
if err != nil {
return nil, err
}
c.cached = ret
return c.cached, nil
return []string{pattern}, nil
}
type cachingCategoryExpanderFunc struct {

View File

@@ -56,3 +56,14 @@ func (clientConfigFn ClientConfigFunc) unstructuredClientForGroupVersion(gv sche
return rest.RESTClientFor(cfg)
}
func (clientConfigFn ClientConfigFunc) withStdinUnavailable(stdinUnavailable bool) ClientConfigFunc {
return func() (*rest.Config, error) {
cfg, err := clientConfigFn()
if stdinUnavailable && cfg != nil && cfg.ExecProvider != nil {
cfg.ExecProvider.StdinUnavailable = stdinUnavailable
cfg.ExecProvider.StdinUnavailableMessage = "used by stdin resource manifest reader"
}
return cfg, err
}
}

View File

@@ -35,7 +35,7 @@ func CRDFromDynamic(client dynamic.Interface) CRDGetter {
return func() ([]schema.GroupKind, error) {
list, err := client.Resource(schema.GroupVersionResource{
Group: "apiextensions.k8s.io",
Version: "v1beta1",
Version: "v1",
Resource: "customresourcedefinitions",
}).List(context.TODO(), metav1.ListOptions{})
if err != nil {

View File

@@ -1,114 +0,0 @@
/*
Copyright 2019 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 resource
import (
"errors"
"fmt"
openapi_v2 "github.com/googleapis/gnostic/openapiv2"
yaml "gopkg.in/yaml.v2"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
)
func NewDryRunVerifier(dynamicClient dynamic.Interface, openAPIGetter discovery.OpenAPISchemaInterface) *DryRunVerifier {
return &DryRunVerifier{
finder: NewCRDFinder(CRDFromDynamic(dynamicClient)),
openAPIGetter: openAPIGetter,
}
}
func hasGVKExtension(extensions []*openapi_v2.NamedAny, gvk schema.GroupVersionKind) bool {
for _, extension := range extensions {
if extension.GetValue().GetYaml() == "" ||
extension.GetName() != "x-kubernetes-group-version-kind" {
continue
}
var value map[string]string
err := yaml.Unmarshal([]byte(extension.GetValue().GetYaml()), &value)
if err != nil {
continue
}
if value["group"] == gvk.Group && value["kind"] == gvk.Kind && value["version"] == gvk.Version {
return true
}
return false
}
return false
}
// DryRunVerifier verifies if a given group-version-kind supports DryRun
// against the current server. Sending dryRun requests to apiserver that
// don't support it will result in objects being unwillingly persisted.
//
// It reads the OpenAPI to see if the given GVK supports dryRun. If the
// GVK can not be found, we assume that CRDs will have the same level of
// support as "namespaces", and non-CRDs will not be supported. We
// delay the check for CRDs as much as possible though, since it
// requires an extra round-trip to the server.
type DryRunVerifier struct {
finder CRDFinder
openAPIGetter discovery.OpenAPISchemaInterface
}
// HasSupport verifies if the given gvk supports DryRun. An error is
// returned if it doesn't.
func (v *DryRunVerifier) HasSupport(gvk schema.GroupVersionKind) error {
oapi, err := v.openAPIGetter.OpenAPISchema()
if err != nil {
return fmt.Errorf("failed to download openapi: %v", err)
}
supports, err := supportsDryRun(oapi, gvk)
if err != nil {
// We assume that we couldn't find the type, then check for namespace:
supports, _ = supportsDryRun(oapi, schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"})
// If namespace supports dryRun, then we will support dryRun for CRDs only.
if supports {
supports, err = v.finder.HasCRD(gvk.GroupKind())
if err != nil {
return fmt.Errorf("failed to check CRD: %v", err)
}
}
}
if !supports {
return fmt.Errorf("%v doesn't support dry-run", gvk)
}
return nil
}
// supportsDryRun is a method that let's us look in the OpenAPI if the
// specific group-version-kind supports the dryRun query parameter for
// the PATCH end-point.
func supportsDryRun(doc *openapi_v2.Document, gvk schema.GroupVersionKind) (bool, error) {
for _, path := range doc.GetPaths().GetPath() {
// Is this describing the gvk we're looking for?
if !hasGVKExtension(path.GetValue().GetPatch().GetVendorExtension(), gvk) {
continue
}
for _, param := range path.GetValue().GetPatch().GetParameters() {
if param.GetParameter().GetNonBodyParameter().GetQueryParameterSubSchema().GetName() == "dryRun" {
return true, nil
}
}
return false, nil
}
return false, errors.New("couldn't find GVK in openapi")
}

View File

@@ -18,9 +18,12 @@ package resource
import (
"context"
"fmt"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
@@ -34,6 +37,8 @@ var metadataAccessor = meta.NewAccessor()
type Helper struct {
// The name of this resource as the server would recognize it
Resource string
// The name of the subresource as the server would recognize it
Subresource string
// A RESTClient capable of mutating this resource.
RESTClient RESTClient
// True if the resource type is scoped to namespaces
@@ -49,6 +54,10 @@ type Helper struct {
// FieldManager is the name associated with the actor or entity that is making
// changes.
FieldManager string
// FieldValidation is the directive used to indicate how the server should perform
// field validation (Ignore, Warn, or Strict)
FieldValidation string
}
// NewHelper creates a Helper from a ResourceMapping
@@ -74,11 +83,25 @@ func (m *Helper) WithFieldManager(fieldManager string) *Helper {
return m
}
// WithFieldValidation sets the field validation option to indicate
// how the server should perform field validation (Ignore, Warn, or Strict).
func (m *Helper) WithFieldValidation(validationDirective string) *Helper {
m.FieldValidation = validationDirective
return m
}
// Subresource sets the helper to access (<resource>/[ns/<namespace>/]<name>/<subresource>)
func (m *Helper) WithSubresource(subresource string) *Helper {
m.Subresource = subresource
return m
}
func (m *Helper) Get(namespace, name string) (runtime.Object, error) {
req := m.RESTClient.Get().
NamespaceIfScoped(namespace, m.NamespaceScoped).
Resource(m.Resource).
Name(name)
Name(name).
SubResource(m.Subresource)
return req.Do(context.TODO()).Get()
}
@@ -90,6 +113,54 @@ func (m *Helper) List(namespace, apiVersion string, options *metav1.ListOptions)
return req.Do(context.TODO()).Get()
}
// FollowContinue handles the continue parameter returned by the API server when using list
// chunking. To take advantage of this, the initial ListOptions provided by the consumer
// should include a non-zero Limit parameter.
func FollowContinue(initialOpts *metav1.ListOptions,
listFunc func(metav1.ListOptions) (runtime.Object, error)) error {
opts := initialOpts
for {
list, err := listFunc(*opts)
if err != nil {
return err
}
nextContinueToken, _ := metadataAccessor.Continue(list)
if len(nextContinueToken) == 0 {
return nil
}
opts.Continue = nextContinueToken
}
}
// EnhanceListError augments errors typically returned by List operations with additional context,
// making sure to retain the StatusError type when applicable.
func EnhanceListError(err error, opts metav1.ListOptions, subj string) error {
if apierrors.IsResourceExpired(err) {
return err
}
if apierrors.IsBadRequest(err) || apierrors.IsNotFound(err) {
if se, ok := err.(*apierrors.StatusError); ok {
// modify the message without hiding this is an API error
if len(opts.LabelSelector) == 0 && len(opts.FieldSelector) == 0 {
se.ErrStatus.Message = fmt.Sprintf("Unable to list %q: %v", subj,
se.ErrStatus.Message)
} else {
se.ErrStatus.Message = fmt.Sprintf(
"Unable to find %q that match label selector %q, field selector %q: %v", subj,
opts.LabelSelector,
opts.FieldSelector, se.ErrStatus.Message)
}
return se
}
if len(opts.LabelSelector) == 0 && len(opts.FieldSelector) == 0 {
return fmt.Errorf("Unable to list %q: %v", subj, err)
}
return fmt.Errorf("Unable to find %q that match label selector %q, field selector %q: %v",
subj, opts.LabelSelector, opts.FieldSelector, err)
}
return err
}
func (m *Helper) Watch(namespace, apiVersion string, options *metav1.ListOptions) (watch.Interface, error) {
options.Watch = true
return m.RESTClient.Get().
@@ -146,6 +217,9 @@ func (m *Helper) CreateWithOptions(namespace string, modify bool, obj runtime.Ob
if m.FieldManager != "" {
options.FieldManager = m.FieldManager
}
if m.FieldValidation != "" {
options.FieldValidation = m.FieldValidation
}
if modify {
// Attempt to version the object based on client logic.
version, err := metadataAccessor.ResourceVersion(obj)
@@ -182,10 +256,14 @@ func (m *Helper) Patch(namespace, name string, pt types.PatchType, data []byte,
if m.FieldManager != "" {
options.FieldManager = m.FieldManager
}
if m.FieldValidation != "" {
options.FieldValidation = m.FieldValidation
}
return m.RESTClient.Patch(pt).
NamespaceIfScoped(namespace, m.NamespaceScoped).
Resource(m.Resource).
Name(name).
SubResource(m.Subresource).
VersionedParams(options, metav1.ParameterCodec).
Body(data).
Do(context.TODO()).
@@ -201,6 +279,9 @@ func (m *Helper) Replace(namespace, name string, overwrite bool, obj runtime.Obj
if m.FieldManager != "" {
options.FieldManager = m.FieldManager
}
if m.FieldValidation != "" {
options.FieldValidation = m.FieldValidation
}
// Attempt to version the object based on client logic.
version, err := metadataAccessor.ResourceVersion(obj)
@@ -210,7 +291,7 @@ func (m *Helper) Replace(namespace, name string, overwrite bool, obj runtime.Obj
}
if version == "" && overwrite {
// Retrieve the current version of the object to overwrite the server object
serverObj, err := c.Get().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(m.Resource).Name(name).Do(context.TODO()).Get()
serverObj, err := c.Get().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(m.Resource).Name(name).SubResource(m.Subresource).Do(context.TODO()).Get()
if err != nil {
// The object does not exist, but we want it to be created
return m.replaceResource(c, m.Resource, namespace, name, obj, options)
@@ -232,6 +313,7 @@ func (m *Helper) replaceResource(c RESTClient, resource, namespace, name string,
NamespaceIfScoped(namespace, m.NamespaceScoped).
Resource(resource).
Name(name).
SubResource(m.Subresource).
VersionedParams(options, metav1.ParameterCodec).
Body(obj).
Do(context.TODO()).

View File

@@ -19,8 +19,8 @@ package resource
import (
"bytes"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/krusty"
"sigs.k8s.io/kustomize/kyaml/filesys"
)
// KustomizeVisitor handles kustomization.yaml files.

View File

@@ -20,6 +20,7 @@ import (
"fmt"
"reflect"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
@@ -64,6 +65,10 @@ func (m *mapper) infoForData(data []byte, source string) (*Info, error) {
}
mapping, err := restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
if _, ok := err.(*meta.NoKindMatchError); ok {
return nil, fmt.Errorf("resource mapping not found for name: %q namespace: %q from %q: %v\nensure CRDs are installed first",
name, namespace, source, err)
}
return nil, fmt.Errorf("unable to recognize %q: %v", source, err)
}
ret.Mapping = mapping

View File

@@ -20,12 +20,9 @@ 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/json"
utiljson "k8s.io/apimachinery/pkg/util/json"
)
// hold a single instance of the case-sensitive decoder
var caseSensitiveJsonIterator = json.CaseSensitiveJSONIterator()
// metadataValidatingDecoder wraps a decoder and additionally ensures metadata schema fields decode before returning an unstructured object
type metadataValidatingDecoder struct {
decoder runtime.Decoder
@@ -47,7 +44,7 @@ func (m *metadataValidatingDecoder) Decode(data []byte, defaults *schema.GroupVe
// make sure the data can decode into ObjectMeta before we return,
// so we don't silently truncate schema errors in metadata later with accesser get/set calls
v := &metadataOnlyObject{}
if typedErr := caseSensitiveJsonIterator.Unmarshal(data, v); typedErr != nil {
if typedErr := utiljson.Unmarshal(data, v); typedErr != nil {
return obj, gvk, typedErr
}
return obj, gvk, err

View File

@@ -0,0 +1,166 @@
/*
Copyright 2019 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 resource
import (
"errors"
"fmt"
openapi_v2 "github.com/google/gnostic/openapiv2"
yaml "gopkg.in/yaml.v2"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
)
func NewQueryParamVerifier(dynamicClient dynamic.Interface, openAPIGetter discovery.OpenAPISchemaInterface, queryParam VerifiableQueryParam) *QueryParamVerifier {
return &QueryParamVerifier{
finder: NewCRDFinder(CRDFromDynamic(dynamicClient)),
openAPIGetter: openAPIGetter,
queryParam: queryParam,
}
}
// QueryParamVerifier verifies if a given group-version-kind supports a
// given VerifiableQueryParam against the current server.
//
// Currently supported query params are:
// 1. dryRun
// 2. fieldValidation
//
// Support for each of these query params needs to be verified because:
//
// 1. Sending dryRun requests to apiserver that
// don't support it will result in objects being unwillingly persisted.
//
// 2. We determine whether or not to perform server-side or client-side
// schema validation based on whether the fieldValidation query param is
// supported or not.
//
// It reads the OpenAPI to see if the given GVK supports the given query param.
// If the GVK can not be found, we assume that CRDs will have the same level of
// support as "namespaces", and non-CRDs will not be supported. We
// delay the check for CRDs as much as possible though, since it
// requires an extra round-trip to the server.
type QueryParamVerifier struct {
finder CRDFinder
openAPIGetter discovery.OpenAPISchemaInterface
queryParam VerifiableQueryParam
}
// Verifier is the generic verifier interface used for testing QueryParamVerifier
type Verifier interface {
HasSupport(gvk schema.GroupVersionKind) error
}
// VerifiableQueryParam is a query parameter who's enablement on the
// apiserver can be determined by evaluating the OpenAPI for a specific
// GVK.
type VerifiableQueryParam string
const (
QueryParamDryRun VerifiableQueryParam = "dryRun"
QueryParamFieldValidation VerifiableQueryParam = "fieldValidation"
)
// HasSupport checks if the given gvk supports the query param configured on v
func (v *QueryParamVerifier) HasSupport(gvk schema.GroupVersionKind) error {
oapi, err := v.openAPIGetter.OpenAPISchema()
if err != nil {
return fmt.Errorf("failed to download openapi: %v", err)
}
supports, err := supportsQueryParam(oapi, gvk, v.queryParam)
if err != nil {
// We assume that we couldn't find the type, then check for namespace:
supports, _ = supportsQueryParam(oapi, schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"}, v.queryParam)
// If namespace supports the query param, then we will support the query param for CRDs only.
if supports {
supports, err = v.finder.HasCRD(gvk.GroupKind())
if err != nil {
return fmt.Errorf("failed to check CRD: %v", err)
}
}
}
if !supports {
return NewParamUnsupportedError(gvk, v.queryParam)
}
return nil
}
type paramUnsupportedError struct {
gvk schema.GroupVersionKind
param VerifiableQueryParam
}
func NewParamUnsupportedError(gvk schema.GroupVersionKind, param VerifiableQueryParam) error {
return &paramUnsupportedError{
gvk: gvk,
param: param,
}
}
func (e *paramUnsupportedError) Error() string {
return fmt.Sprintf("%v doesn't support %s", e.gvk, e.param)
}
func IsParamUnsupportedError(err error) bool {
if err == nil {
return false
}
_, ok := err.(*paramUnsupportedError)
return ok
}
func hasGVKExtension(extensions []*openapi_v2.NamedAny, gvk schema.GroupVersionKind) bool {
for _, extension := range extensions {
if extension.GetValue().GetYaml() == "" ||
extension.GetName() != "x-kubernetes-group-version-kind" {
continue
}
var value map[string]string
err := yaml.Unmarshal([]byte(extension.GetValue().GetYaml()), &value)
if err != nil {
continue
}
if value["group"] == gvk.Group && value["kind"] == gvk.Kind && value["version"] == gvk.Version {
return true
}
return false
}
return false
}
// supportsQueryParam is a method that let's us look in the OpenAPI if the
// specific group-version-kind supports the specific query parameter for
// the PATCH end-point.
func supportsQueryParam(doc *openapi_v2.Document, gvk schema.GroupVersionKind, queryParam VerifiableQueryParam) (bool, error) {
for _, path := range doc.GetPaths().GetPath() {
// Is this describing the gvk we're looking for?
if !hasGVKExtension(path.GetValue().GetPatch().GetVendorExtension(), gvk) {
continue
}
for _, param := range path.GetValue().GetPatch().GetParameters() {
if param.GetParameter().GetNonBodyParameter().GetQueryParameterSubSchema().GetName() == string(queryParam) {
return true, nil
}
}
return false, nil
}
return false, errors.New("couldn't find GVK in openapi")
}

View File

@@ -17,11 +17,9 @@ limitations under the License.
package resource
import (
"fmt"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
)
@@ -49,41 +47,23 @@ func NewSelector(client RESTClient, mapping *meta.RESTMapping, namespace, labelS
// Visit implements Visitor and uses request chunking by default.
func (r *Selector) Visit(fn VisitorFunc) error {
var continueToken string
for {
list, err := NewHelper(r.Client, r.Mapping).List(
helper := NewHelper(r.Client, r.Mapping)
initialOpts := metav1.ListOptions{
LabelSelector: r.LabelSelector,
FieldSelector: r.FieldSelector,
Limit: r.LimitChunks,
}
return FollowContinue(&initialOpts, func(options metav1.ListOptions) (runtime.Object, error) {
list, err := helper.List(
r.Namespace,
r.ResourceMapping().GroupVersionKind.GroupVersion().String(),
&metav1.ListOptions{
LabelSelector: r.LabelSelector,
FieldSelector: r.FieldSelector,
Limit: r.LimitChunks,
Continue: continueToken,
},
&options,
)
if err != nil {
if errors.IsResourceExpired(err) {
return err
}
if errors.IsBadRequest(err) || errors.IsNotFound(err) {
if se, ok := err.(*errors.StatusError); ok {
// modify the message without hiding this is an API error
if len(r.LabelSelector) == 0 && len(r.FieldSelector) == 0 {
se.ErrStatus.Message = fmt.Sprintf("Unable to list %q: %v", r.Mapping.Resource, se.ErrStatus.Message)
} else {
se.ErrStatus.Message = fmt.Sprintf("Unable to find %q that match label selector %q, field selector %q: %v", r.Mapping.Resource, r.LabelSelector, r.FieldSelector, se.ErrStatus.Message)
}
return se
}
if len(r.LabelSelector) == 0 && len(r.FieldSelector) == 0 {
return fmt.Errorf("Unable to list %q: %v", r.Mapping.Resource, err)
}
return fmt.Errorf("Unable to find %q that match label selector %q, field selector %q: %v", r.Mapping.Resource, r.LabelSelector, r.FieldSelector, err)
}
return err
return nil, EnhanceListError(err, options, r.Mapping.Resource.String())
}
resourceVersion, _ := metadataAccessor.ResourceVersion(list)
nextContinueToken, _ := metadataAccessor.Continue(list)
info := &Info{
Client: r.Client,
Mapping: r.Mapping,
@@ -95,13 +75,10 @@ func (r *Selector) Visit(fn VisitorFunc) error {
}
if err := fn(info, nil); err != nil {
return err
return nil, err
}
if len(nextContinueToken) == 0 {
return nil
}
continueToken = nextContinueToken
}
return list, nil
})
}
func (r *Selector) Watch(resourceVersion string) (watch.Interface, error) {

View File

@@ -78,12 +78,16 @@ type Info struct {
// defined. If retrieved from the server, the Builder expects the mapping client to
// decide the final form. Use the AsVersioned, AsUnstructured, and AsInternal helpers
// to alter the object versions.
// If Subresource is specified, this will be the object for the subresource.
Object runtime.Object
// Optional, this is the most recent resource version the server knows about for
// this type of resource. It may not match the resource version of the object,
// but if set it should be equal to or newer than the resource version of the
// object (however the server defines resource version).
ResourceVersion string
// Optional, if specified, the object is the most recent value of the subresource
// returned by the server if available.
Subresource string
}
// Visit implements Visitor
@@ -93,7 +97,7 @@ func (i *Info) Visit(fn VisitorFunc) error {
// Get retrieves the object from the Namespace and Name fields
func (i *Info) Get() (err error) {
obj, err := NewHelper(i.Client, i.Mapping).Get(i.Namespace, i.Name)
obj, err := NewHelper(i.Client, i.Mapping).WithSubresource(i.Subresource).Get(i.Namespace, i.Name)
if err != nil {
if errors.IsNotFound(err) && len(i.Namespace) > 0 && i.Namespace != metav1.NamespaceDefault && i.Namespace != metav1.NamespaceAll {
err2 := i.Client.Get().AbsPath("api", "v1", "namespaces", i.Namespace).Do(context.TODO()).Error()
@@ -204,9 +208,9 @@ type EagerVisitorList []Visitor
// Visit implements Visitor, and gathers errors that occur during processing until
// all sub visitors have been visited.
func (l EagerVisitorList) Visit(fn VisitorFunc) error {
errs := []error(nil)
var errs []error
for i := range l {
if err := l[i].Visit(func(info *Info, err error) error {
err := l[i].Visit(func(info *Info, err error) error {
if err != nil {
errs = append(errs, err)
return nil
@@ -215,7 +219,8 @@ func (l EagerVisitorList) Visit(fn VisitorFunc) error {
errs = append(errs, err)
}
return nil
}); err != nil {
})
if err != nil {
errs = append(errs, err)
}
}
@@ -253,13 +258,15 @@ func (v *URLVisitor) Visit(fn VisitorFunc) error {
// readHttpWithRetries tries to http.Get the v.URL retries times before giving up.
func readHttpWithRetries(get httpget, duration time.Duration, u string, attempts int) (io.ReadCloser, error) {
var err error
var body io.ReadCloser
if attempts <= 0 {
return nil, fmt.Errorf("http attempts must be greater than 0, was %d", attempts)
}
for i := 0; i < attempts; i++ {
var statusCode int
var status string
var (
statusCode int
status string
body io.ReadCloser
)
if i > 0 {
time.Sleep(duration)
}
@@ -272,10 +279,12 @@ func readHttpWithRetries(get httpget, duration time.Duration, u string, attempts
continue
}
// Error - Set the error condition from the StatusCode
if statusCode != http.StatusOK {
err = fmt.Errorf("unable to read URL %q, server reported %s, status code=%d", u, status, statusCode)
if statusCode == http.StatusOK {
return body, nil
}
body.Close()
// Error - Set the error condition from the StatusCode
err = fmt.Errorf("unable to read URL %q, server reported %s, status code=%d", u, status, statusCode)
if statusCode >= 500 && statusCode < 600 {
// Retry 500's
@@ -285,7 +294,7 @@ func readHttpWithRetries(get httpget, duration time.Duration, u string, attempts
break
}
}
return body, err
return nil, err
}
// httpget Defines function to retrieve a url and return the results. Exists for unit test stubbing.
@@ -346,7 +355,7 @@ type ContinueOnErrorVisitor struct {
// returned by the visitor directly may still result in some items
// not being visited.
func (v ContinueOnErrorVisitor) Visit(fn VisitorFunc) error {
errs := []error{}
var errs []error
err := v.Visitor.Visit(func(info *Info, err error) error {
if err != nil {
errs = append(errs, err)
@@ -420,7 +429,7 @@ func (v FlattenListVisitor) Visit(fn VisitorFunc) error {
if info.Mapping != nil && !info.Mapping.GroupVersionKind.Empty() {
preferredGVKs = append(preferredGVKs, info.Mapping.GroupVersionKind)
}
errs := []error{}
var errs []error
for i := range items {
item, err := v.mapper.infoForObject(items[i], v.typer, preferredGVKs)
if err != nil {
@@ -430,12 +439,15 @@ func (v FlattenListVisitor) Visit(fn VisitorFunc) error {
if len(info.ResourceVersion) != 0 {
item.ResourceVersion = info.ResourceVersion
}
// propagate list source to items source
if len(info.Source) != 0 {
item.Source = info.Source
}
if err := fn(item, nil); err != nil {
errs = append(errs, err)
}
}
return utilerrors.NewAggregate(errs)
})
}
@@ -671,16 +683,6 @@ func RetrieveLazy(info *Info, err error) error {
return nil
}
// CreateAndRefresh creates an object from input info and refreshes info with that object
func CreateAndRefresh(info *Info) error {
obj, err := NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
if err != nil {
return err
}
info.Refresh(obj, true)
return nil
}
type FilterFunc func(info *Info, err error) (bool, error)
type FilteredVisitor struct {