mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 19:01:49 +00:00
Merge pull request #86408 from julianvmodesto/kubectl-ss-dry-run-helper
Support server-side dry-run in cli-runtime REST Helper
This commit is contained in:
commit
c01451585e
110465
staging/src/k8s.io/cli-runtime/artifacts/openapi/swagger.json
Normal file
110465
staging/src/k8s.io/cli-runtime/artifacts/openapi/swagger.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,7 @@ require (
|
|||||||
github.com/ghodss/yaml v1.0.0 // indirect
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.19.3 // indirect
|
github.com/go-openapi/jsonreference v0.19.3 // indirect
|
||||||
github.com/go-openapi/spec v0.19.3 // indirect
|
github.com/go-openapi/spec v0.19.3 // indirect
|
||||||
|
github.com/googleapis/gnostic v0.1.0
|
||||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de
|
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de
|
||||||
github.com/mailru/easyjson v0.7.0 // indirect
|
github.com/mailru/easyjson v0.7.0 // indirect
|
||||||
github.com/pkg/errors v0.8.1
|
github.com/pkg/errors v0.8.1
|
||||||
@ -18,9 +19,11 @@ require (
|
|||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.4.0
|
github.com/stretchr/testify v1.4.0
|
||||||
golang.org/x/text v0.3.2
|
golang.org/x/text v0.3.2
|
||||||
|
gopkg.in/yaml.v2 v2.2.7
|
||||||
k8s.io/api v0.0.0
|
k8s.io/api v0.0.0
|
||||||
k8s.io/apimachinery v0.0.0
|
k8s.io/apimachinery v0.0.0
|
||||||
k8s.io/client-go v0.0.0
|
k8s.io/client-go v0.0.0
|
||||||
|
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a
|
||||||
sigs.k8s.io/kustomize v2.0.3+incompatible
|
sigs.k8s.io/kustomize v2.0.3+incompatible
|
||||||
sigs.k8s.io/yaml v1.1.0
|
sigs.k8s.io/yaml v1.1.0
|
||||||
)
|
)
|
||||||
|
1
staging/src/k8s.io/cli-runtime/go.sum
generated
1
staging/src/k8s.io/cli-runtime/go.sum
generated
@ -36,6 +36,7 @@ github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6
|
|||||||
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680 h1:ZktWZesgun21uEDrwW7iEV1zPCGQldM2atlJZ3TdvVM=
|
||||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
@ -5,7 +5,9 @@ go_library(
|
|||||||
srcs = [
|
srcs = [
|
||||||
"builder.go",
|
"builder.go",
|
||||||
"client.go",
|
"client.go",
|
||||||
|
"crd_finder.go",
|
||||||
"doc.go",
|
"doc.go",
|
||||||
|
"dry_run_verifier.go",
|
||||||
"fake.go",
|
"fake.go",
|
||||||
"helper.go",
|
"helper.go",
|
||||||
"interfaces.go",
|
"interfaces.go",
|
||||||
@ -39,11 +41,14 @@ go_library(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||||
"//staging/src/k8s.io/cli-runtime/pkg/kustomize:go_default_library",
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/discovery:go_default_library",
|
"//staging/src/k8s.io/client-go/discovery:go_default_library",
|
||||||
|
"//staging/src/k8s.io/client-go/dynamic:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library",
|
"//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/restmapper:go_default_library",
|
"//staging/src/k8s.io/client-go/restmapper:go_default_library",
|
||||||
|
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
|
||||||
"//vendor/golang.org/x/text/encoding/unicode:go_default_library",
|
"//vendor/golang.org/x/text/encoding/unicode:go_default_library",
|
||||||
"//vendor/golang.org/x/text/transform:go_default_library",
|
"//vendor/golang.org/x/text/transform:go_default_library",
|
||||||
|
"//vendor/gopkg.in/yaml.v2:go_default_library",
|
||||||
"//vendor/sigs.k8s.io/kustomize/pkg/fs:go_default_library",
|
"//vendor/sigs.k8s.io/kustomize/pkg/fs:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -52,6 +57,8 @@ go_test(
|
|||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = [
|
srcs = [
|
||||||
"builder_test.go",
|
"builder_test.go",
|
||||||
|
"crd_finder_test.go",
|
||||||
|
"dry_run_verifier_test.go",
|
||||||
"helper_test.go",
|
"helper_test.go",
|
||||||
"scheme_test.go",
|
"scheme_test.go",
|
||||||
"visitor_test.go",
|
"visitor_test.go",
|
||||||
@ -81,7 +88,9 @@ go_test(
|
|||||||
"//staging/src/k8s.io/client-go/restmapper:go_default_library",
|
"//staging/src/k8s.io/client-go/restmapper:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/util/testing:go_default_library",
|
"//staging/src/k8s.io/client-go/util/testing:go_default_library",
|
||||||
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
|
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
|
||||||
|
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
|
||||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||||
|
"//vendor/k8s.io/kube-openapi/pkg/util/proto/testing:go_default_library",
|
||||||
"//vendor/sigs.k8s.io/yaml:go_default_library",
|
"//vendor/sigs.k8s.io/yaml:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package util
|
package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -14,14 +14,13 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package util_test
|
package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/kubectl/pkg/cmd/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCacheCRDFinder(t *testing.T) {
|
func TestCacheCRDFinder(t *testing.T) {
|
||||||
@ -30,7 +29,7 @@ func TestCacheCRDFinder(t *testing.T) {
|
|||||||
called += 1
|
called += 1
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
finder := util.NewCRDFinder(getter)
|
finder := NewCRDFinder(getter)
|
||||||
if called != 0 {
|
if called != 0 {
|
||||||
t.Fatalf("Creating the finder shouldn't call the getter, has called = %v", called)
|
t.Fatalf("Creating the finder shouldn't call the getter, has called = %v", called)
|
||||||
}
|
}
|
||||||
@ -55,7 +54,7 @@ func TestCRDFinderErrors(t *testing.T) {
|
|||||||
getter := func() ([]schema.GroupKind, error) {
|
getter := func() ([]schema.GroupKind, error) {
|
||||||
return nil, errors.New("not working")
|
return nil, errors.New("not working")
|
||||||
}
|
}
|
||||||
finder := util.NewCRDFinder(getter)
|
finder := NewCRDFinder(getter)
|
||||||
found, err := finder.HasCRD(schema.GroupKind{Group: "", Kind: "Pod"})
|
found, err := finder.HasCRD(schema.GroupKind{Group: "", Kind: "Pod"})
|
||||||
if found == true {
|
if found == true {
|
||||||
t.Fatalf("Found the CRD with non-working getter function")
|
t.Fatalf("Found the CRD with non-working getter function")
|
||||||
@ -78,7 +77,7 @@ func TestCRDFinder(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
finder := util.NewCRDFinder(getter)
|
finder := NewCRDFinder(getter)
|
||||||
|
|
||||||
if found, _ := finder.HasCRD(schema.GroupKind{Group: "crd.com", Kind: "MyCRD"}); !found {
|
if found, _ := finder.HasCRD(schema.GroupKind{Group: "crd.com", Kind: "MyCRD"}); !found {
|
||||||
t.Fatalf("Failed to find CRD MyCRD")
|
t.Fatalf("Failed to find CRD MyCRD")
|
121
staging/src/k8s.io/cli-runtime/pkg/resource/dry_run_verifier.go
Normal file
121
staging/src/k8s.io/cli-runtime/pkg/resource/dry_run_verifier.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VerifyDryRun returns nil if a resource group-version-kind supports
|
||||||
|
// server-side dry-run. Otherwise, an error is returned.
|
||||||
|
func VerifyDryRun(gvk schema.GroupVersionKind, dynamicClient dynamic.Interface, discoveryClient discovery.DiscoveryInterface) error {
|
||||||
|
verifier := NewDryRunVerifier(dynamicClient, discoveryClient)
|
||||||
|
return verifier.HasSupport(gvk)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDryRunVerifier(dynamicClient dynamic.Interface, discoveryClient discovery.DiscoveryInterface) *DryRunVerifier {
|
||||||
|
return &DryRunVerifier{
|
||||||
|
Finder: NewCRDFinder(CRDFromDynamic(dynamicClient)),
|
||||||
|
OpenAPIGetter: discoveryClient,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
openapitesting "k8s.io/kube-openapi/pkg/util/proto/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSupportsDryRun(t *testing.T) {
|
||||||
|
doc, err := fakeSchema.OpenAPISchema()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get OpenAPI Schema: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
gvk schema.GroupVersionKind
|
||||||
|
success bool
|
||||||
|
supports bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
gvk: schema.GroupVersionKind{
|
||||||
|
Group: "",
|
||||||
|
Version: "v1",
|
||||||
|
Kind: "Pod",
|
||||||
|
},
|
||||||
|
success: true,
|
||||||
|
supports: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gvk: schema.GroupVersionKind{
|
||||||
|
Group: "",
|
||||||
|
Version: "v1",
|
||||||
|
Kind: "UnknownKind",
|
||||||
|
},
|
||||||
|
success: false,
|
||||||
|
supports: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gvk: schema.GroupVersionKind{
|
||||||
|
Group: "",
|
||||||
|
Version: "v1",
|
||||||
|
Kind: "NodeProxyOptions",
|
||||||
|
},
|
||||||
|
success: true,
|
||||||
|
supports: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
supports, err := SupportsDryRun(doc, test.gvk)
|
||||||
|
if supports != test.supports || ((err == nil) != test.success) {
|
||||||
|
errStr := "nil"
|
||||||
|
if test.success == false {
|
||||||
|
errStr = "err"
|
||||||
|
}
|
||||||
|
t.Errorf("SupportsDryRun(doc, %v) = (%v, %v), expected (%v, %v)",
|
||||||
|
test.gvk,
|
||||||
|
supports, err,
|
||||||
|
test.supports, errStr,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fakeSchema = openapitesting.Fake{Path: filepath.Join("..", "..", "artifacts", "openapi", "swagger.json")}
|
||||||
|
|
||||||
|
func TestDryRunVerifier(t *testing.T) {
|
||||||
|
dryRunVerifier := DryRunVerifier{
|
||||||
|
Finder: NewCRDFinder(func() ([]schema.GroupKind, error) {
|
||||||
|
return []schema.GroupKind{
|
||||||
|
{
|
||||||
|
Group: "crd.com",
|
||||||
|
Kind: "MyCRD",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Group: "crd.com",
|
||||||
|
Kind: "MyNewCRD",
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}),
|
||||||
|
OpenAPIGetter: &fakeSchema,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := dryRunVerifier.HasSupport(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "NodeProxyOptions"})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("NodeProxyOptions doesn't support dry-run, yet no error found")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dryRunVerifier.HasSupport(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Pod should support dry-run: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dryRunVerifier.HasSupport(schema.GroupVersionKind{Group: "crd.com", Version: "v1", Kind: "MyCRD"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("MyCRD should support dry-run: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dryRunVerifier.HasSupport(schema.GroupVersionKind{Group: "crd.com", Version: "v1", Kind: "Random"})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Random doesn't support dry-run, yet no error found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmptyOpenAPI struct{}
|
||||||
|
|
||||||
|
func (EmptyOpenAPI) OpenAPISchema() (*openapi_v2.Document, error) {
|
||||||
|
return &openapi_v2.Document{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDryRunVerifierNoOpenAPI(t *testing.T) {
|
||||||
|
dryRunVerifier := DryRunVerifier{
|
||||||
|
Finder: NewCRDFinder(func() ([]schema.GroupKind, error) {
|
||||||
|
return []schema.GroupKind{
|
||||||
|
{
|
||||||
|
Group: "crd.com",
|
||||||
|
Kind: "MyCRD",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Group: "crd.com",
|
||||||
|
Kind: "MyNewCRD",
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}),
|
||||||
|
OpenAPIGetter: EmptyOpenAPI{},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := dryRunVerifier.HasSupport(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Pod doesn't support dry-run, yet no error found")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dryRunVerifier.HasSupport(schema.GroupVersionKind{Group: "crd.com", Version: "v1", Kind: "MyCRD"})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("MyCRD doesn't support dry-run, yet no error found")
|
||||||
|
}
|
||||||
|
}
|
@ -38,6 +38,13 @@ type Helper struct {
|
|||||||
RESTClient RESTClient
|
RESTClient RESTClient
|
||||||
// True if the resource type is scoped to namespaces
|
// True if the resource type is scoped to namespaces
|
||||||
NamespaceScoped bool
|
NamespaceScoped bool
|
||||||
|
// If true, then use server-side dry-run to not persist changes to storage
|
||||||
|
// for verbs and resources that support server-side dry-run.
|
||||||
|
//
|
||||||
|
// Note this should only be used against an apiserver with dry-run enabled,
|
||||||
|
// and on resources that support dry-run. If the apiserver or the resource
|
||||||
|
// does not support dry-run, then the change will be persisted to storage.
|
||||||
|
ServerDryRun bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHelper creates a Helper from a ResourceMapping
|
// NewHelper creates a Helper from a ResourceMapping
|
||||||
@ -49,6 +56,13 @@ func NewHelper(client RESTClient, mapping *meta.RESTMapping) *Helper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DryRun, if true, will use server-side dry-run to not persist changes to storage.
|
||||||
|
// Otherwise, changes will be persisted to storage.
|
||||||
|
func (m *Helper) DryRun(dryRun bool) *Helper {
|
||||||
|
m.ServerDryRun = dryRun
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Helper) Get(namespace, name string, export bool) (runtime.Object, error) {
|
func (m *Helper) Get(namespace, name string, export bool) (runtime.Object, error) {
|
||||||
req := m.RESTClient.Get().
|
req := m.RESTClient.Get().
|
||||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||||
@ -99,6 +113,13 @@ func (m *Helper) Delete(namespace, name string) (runtime.Object, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Helper) DeleteWithOptions(namespace, name string, options *metav1.DeleteOptions) (runtime.Object, error) {
|
func (m *Helper) DeleteWithOptions(namespace, name string, options *metav1.DeleteOptions) (runtime.Object, error) {
|
||||||
|
if options == nil {
|
||||||
|
options = &metav1.DeleteOptions{}
|
||||||
|
}
|
||||||
|
if m.ServerDryRun {
|
||||||
|
options.DryRun = []string{metav1.DryRunAll}
|
||||||
|
}
|
||||||
|
|
||||||
return m.RESTClient.Delete().
|
return m.RESTClient.Delete().
|
||||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||||
Resource(m.Resource).
|
Resource(m.Resource).
|
||||||
@ -108,10 +129,17 @@ func (m *Helper) DeleteWithOptions(namespace, name string, options *metav1.Delet
|
|||||||
Get()
|
Get()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Helper) Create(namespace string, modify bool, obj runtime.Object, options *metav1.CreateOptions) (runtime.Object, error) {
|
func (m *Helper) Create(namespace string, modify bool, obj runtime.Object) (runtime.Object, error) {
|
||||||
|
return m.CreateWithOptions(namespace, modify, obj, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Helper) CreateWithOptions(namespace string, modify bool, obj runtime.Object, options *metav1.CreateOptions) (runtime.Object, error) {
|
||||||
if options == nil {
|
if options == nil {
|
||||||
options = &metav1.CreateOptions{}
|
options = &metav1.CreateOptions{}
|
||||||
}
|
}
|
||||||
|
if m.ServerDryRun {
|
||||||
|
options.DryRun = []string{metav1.DryRunAll}
|
||||||
|
}
|
||||||
if modify {
|
if modify {
|
||||||
// Attempt to version the object based on client logic.
|
// Attempt to version the object based on client logic.
|
||||||
version, err := metadataAccessor.ResourceVersion(obj)
|
version, err := metadataAccessor.ResourceVersion(obj)
|
||||||
@ -142,6 +170,9 @@ func (m *Helper) Patch(namespace, name string, pt types.PatchType, data []byte,
|
|||||||
if options == nil {
|
if options == nil {
|
||||||
options = &metav1.PatchOptions{}
|
options = &metav1.PatchOptions{}
|
||||||
}
|
}
|
||||||
|
if m.ServerDryRun {
|
||||||
|
options.DryRun = []string{metav1.DryRunAll}
|
||||||
|
}
|
||||||
return m.RESTClient.Patch(pt).
|
return m.RESTClient.Patch(pt).
|
||||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||||
Resource(m.Resource).
|
Resource(m.Resource).
|
||||||
@ -154,19 +185,23 @@ func (m *Helper) Patch(namespace, name string, pt types.PatchType, data []byte,
|
|||||||
|
|
||||||
func (m *Helper) Replace(namespace, name string, overwrite bool, obj runtime.Object) (runtime.Object, error) {
|
func (m *Helper) Replace(namespace, name string, overwrite bool, obj runtime.Object) (runtime.Object, error) {
|
||||||
c := m.RESTClient
|
c := m.RESTClient
|
||||||
|
var options = &metav1.UpdateOptions{}
|
||||||
|
if m.ServerDryRun {
|
||||||
|
options.DryRun = []string{metav1.DryRunAll}
|
||||||
|
}
|
||||||
|
|
||||||
// Attempt to version the object based on client logic.
|
// Attempt to version the object based on client logic.
|
||||||
version, err := metadataAccessor.ResourceVersion(obj)
|
version, err := metadataAccessor.ResourceVersion(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// We don't know how to version this object, so send it to the server as is
|
// We don't know how to version this object, so send it to the server as is
|
||||||
return m.replaceResource(c, m.Resource, namespace, name, obj)
|
return m.replaceResource(c, m.Resource, namespace, name, obj, options)
|
||||||
}
|
}
|
||||||
if version == "" && overwrite {
|
if version == "" && overwrite {
|
||||||
// Retrieve the current version of the object to overwrite the server object
|
// 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().Get()
|
serverObj, err := c.Get().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(m.Resource).Name(name).Do().Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// The object does not exist, but we want it to be created
|
// The object does not exist, but we want it to be created
|
||||||
return m.replaceResource(c, m.Resource, namespace, name, obj)
|
return m.replaceResource(c, m.Resource, namespace, name, obj, options)
|
||||||
}
|
}
|
||||||
serverVersion, err := metadataAccessor.ResourceVersion(serverObj)
|
serverVersion, err := metadataAccessor.ResourceVersion(serverObj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -177,9 +212,16 @@ func (m *Helper) Replace(namespace, name string, overwrite bool, obj runtime.Obj
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.replaceResource(c, m.Resource, namespace, name, obj)
|
return m.replaceResource(c, m.Resource, namespace, name, obj, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Helper) replaceResource(c RESTClient, resource, namespace, name string, obj runtime.Object) (runtime.Object, error) {
|
func (m *Helper) replaceResource(c RESTClient, resource, namespace, name string, obj runtime.Object, options *metav1.UpdateOptions) (runtime.Object, error) {
|
||||||
return c.Put().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(resource).Name(name).Body(obj).Do().Get()
|
return c.Put().
|
||||||
|
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||||
|
Resource(resource).
|
||||||
|
Name(name).
|
||||||
|
VersionedParams(options, metav1.ParameterCodec).
|
||||||
|
Body(obj).
|
||||||
|
Do().
|
||||||
|
Get()
|
||||||
}
|
}
|
||||||
|
@ -120,7 +120,7 @@ func TestHelperDelete(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
client := &fake.RESTClient{
|
client := &fake.RESTClient{
|
||||||
NegotiatedSerializer: scheme.Codecs,
|
NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
|
||||||
Resp: tt.Resp,
|
Resp: tt.Resp,
|
||||||
Err: tt.HttpErr,
|
Err: tt.HttpErr,
|
||||||
}
|
}
|
||||||
@ -227,7 +227,7 @@ func TestHelperCreate(t *testing.T) {
|
|||||||
RESTClient: client,
|
RESTClient: client,
|
||||||
NamespaceScoped: true,
|
NamespaceScoped: true,
|
||||||
}
|
}
|
||||||
_, err := modifier.Create("bar", tt.Modify, tt.Object, nil)
|
_, err := modifier.Create("bar", tt.Modify, tt.Object)
|
||||||
if (err != nil) != tt.Err {
|
if (err != nil) != tt.Err {
|
||||||
t.Errorf("%d: unexpected error: %t %v", i, tt.Err, err)
|
t.Errorf("%d: unexpected error: %t %v", i, tt.Err, err)
|
||||||
}
|
}
|
||||||
|
@ -695,7 +695,7 @@ func RetrieveLazy(info *Info, err error) error {
|
|||||||
|
|
||||||
// CreateAndRefresh creates an object from input info and refreshes info with that object
|
// CreateAndRefresh creates an object from input info and refreshes info with that object
|
||||||
func CreateAndRefresh(info *Info) error {
|
func CreateAndRefresh(info *Info) error {
|
||||||
obj, err := NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object, nil)
|
obj, err := NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,6 @@ go_test(
|
|||||||
"//staging/src/k8s.io/kubectl/pkg/cmd/util:go_default_library",
|
"//staging/src/k8s.io/kubectl/pkg/cmd/util:go_default_library",
|
||||||
"//staging/src/k8s.io/kubectl/pkg/scheme:go_default_library",
|
"//staging/src/k8s.io/kubectl/pkg/scheme:go_default_library",
|
||||||
"//staging/src/k8s.io/kubectl/pkg/util/openapi:go_default_library",
|
"//staging/src/k8s.io/kubectl/pkg/util/openapi:go_default_library",
|
||||||
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
|
|
||||||
"//vendor/github.com/spf13/cobra:go_default_library",
|
"//vendor/github.com/spf13/cobra:go_default_library",
|
||||||
"//vendor/k8s.io/utils/pointer:go_default_library",
|
"//vendor/k8s.io/utils/pointer:go_default_library",
|
||||||
],
|
],
|
||||||
|
@ -27,7 +27,6 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
@ -207,10 +206,20 @@ func NewCmdApply(baseName string, f cmdutil.Factory, ioStreams genericclioptions
|
|||||||
|
|
||||||
// Complete verifies if ApplyOptions are valid and without conflicts.
|
// Complete verifies if ApplyOptions are valid and without conflicts.
|
||||||
func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
|
func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
|
||||||
|
var err error
|
||||||
o.ServerSideApply = cmdutil.GetServerSideApplyFlag(cmd)
|
o.ServerSideApply = cmdutil.GetServerSideApplyFlag(cmd)
|
||||||
o.ForceConflicts = cmdutil.GetForceConflictsFlag(cmd)
|
o.ForceConflicts = cmdutil.GetForceConflictsFlag(cmd)
|
||||||
o.FieldManager = cmdutil.GetFieldManagerFlag(cmd)
|
o.FieldManager = cmdutil.GetFieldManagerFlag(cmd)
|
||||||
o.DryRun = cmdutil.GetDryRunFlag(cmd)
|
o.DryRun = cmdutil.GetDryRunFlag(cmd)
|
||||||
|
o.DynamicClient, err = f.DynamicClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
o.DiscoveryClient, err = f.ToDiscoveryClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if o.ForceConflicts && !o.ServerSideApply {
|
if o.ForceConflicts && !o.ServerSideApply {
|
||||||
return fmt.Errorf("--force-conflicts only works with --server-side")
|
return fmt.Errorf("--force-conflicts only works with --server-side")
|
||||||
@ -236,23 +245,13 @@ func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
|
|||||||
return o.PrintFlags.ToPrinter()
|
return o.PrintFlags.ToPrinter()
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
o.RecordFlags.Complete(cmd)
|
o.RecordFlags.Complete(cmd)
|
||||||
o.Recorder, err = o.RecordFlags.ToRecorder()
|
o.Recorder, err = o.RecordFlags.ToRecorder()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
o.DiscoveryClient, err = f.ToDiscoveryClient()
|
o.DeleteOptions = o.DeleteFlags.ToOptions(o.DynamicClient, o.IOStreams)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dynamicClient, err := f.DynamicClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
o.DeleteOptions = o.DeleteFlags.ToOptions(dynamicClient, o.IOStreams)
|
|
||||||
err = o.DeleteOptions.FilenameOptions.RequireFilenameOrKustomize()
|
err = o.DeleteOptions.FilenameOptions.RequireFilenameOrKustomize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -269,11 +268,6 @@ func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
o.DynamicClient, err = f.DynamicClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -360,11 +354,6 @@ func (o *ApplyOptions) SetObjects(infos []*resource.Info) {
|
|||||||
// Run executes the `apply` command.
|
// Run executes the `apply` command.
|
||||||
func (o *ApplyOptions) Run() error {
|
func (o *ApplyOptions) Run() error {
|
||||||
|
|
||||||
dryRunVerifier := &DryRunVerifier{
|
|
||||||
Finder: cmdutil.NewCRDFinder(cmdutil.CRDFromDynamic(o.DynamicClient)),
|
|
||||||
OpenAPIGetter: o.DiscoveryClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.PreProcessorFn != nil {
|
if o.PreProcessorFn != nil {
|
||||||
klog.V(4).Infof("Running apply pre-processor function")
|
klog.V(4).Infof("Running apply pre-processor function")
|
||||||
if err := o.PreProcessorFn(); err != nil {
|
if err := o.PreProcessorFn(); err != nil {
|
||||||
@ -388,13 +377,6 @@ func (o *ApplyOptions) Run() error {
|
|||||||
}
|
}
|
||||||
for _, info := range infos {
|
for _, info := range infos {
|
||||||
|
|
||||||
// If server-dry-run is requested but the type doesn't support it, fail right away.
|
|
||||||
if o.ServerDryRun {
|
|
||||||
if err := dryRunVerifier.HasSupport(info.Mapping.GroupVersionKind); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
o.MarkNamespaceVisited(info)
|
o.MarkNamespaceVisited(info)
|
||||||
|
|
||||||
if err := o.Recorder.Record(info.Object); err != nil {
|
if err := o.Recorder.Record(info.Object); err != nil {
|
||||||
@ -412,11 +394,15 @@ func (o *ApplyOptions) Run() error {
|
|||||||
Force: &o.ForceConflicts,
|
Force: &o.ForceConflicts,
|
||||||
FieldManager: o.FieldManager,
|
FieldManager: o.FieldManager,
|
||||||
}
|
}
|
||||||
if o.ServerDryRun {
|
|
||||||
options.DryRun = []string{metav1.DryRunAll}
|
|
||||||
}
|
|
||||||
|
|
||||||
obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(
|
helper := resource.NewHelper(info.Client, info.Mapping)
|
||||||
|
if o.ServerDryRun {
|
||||||
|
if err := resource.VerifyDryRun(info.Mapping.GroupVersionKind, o.DynamicClient, o.DiscoveryClient); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
helper.DryRun(o.ServerDryRun)
|
||||||
|
}
|
||||||
|
obj, err := helper.Patch(
|
||||||
info.Namespace,
|
info.Namespace,
|
||||||
info.Name,
|
info.Name,
|
||||||
types.ApplyPatchType,
|
types.ApplyPatchType,
|
||||||
@ -486,11 +472,14 @@ See http://k8s.io/docs/reference/using-api/api-concepts/#conflicts`, err)
|
|||||||
|
|
||||||
if !o.DryRun {
|
if !o.DryRun {
|
||||||
// Then create the resource and skip the three-way merge
|
// Then create the resource and skip the three-way merge
|
||||||
options := metav1.CreateOptions{}
|
helper := resource.NewHelper(info.Client, info.Mapping)
|
||||||
if o.ServerDryRun {
|
if o.ServerDryRun {
|
||||||
options.DryRun = []string{metav1.DryRunAll}
|
if err := resource.VerifyDryRun(info.Mapping.GroupVersionKind, o.DynamicClient, o.DiscoveryClient); err != nil {
|
||||||
|
return cmdutil.AddSourceToErr("creating", info.Source, err)
|
||||||
|
}
|
||||||
|
helper.DryRun(o.ServerDryRun)
|
||||||
}
|
}
|
||||||
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object, &options)
|
obj, err := helper.Create(info.Namespace, true, info.Object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cmdutil.AddSourceToErr("creating", info.Source, err)
|
return cmdutil.AddSourceToErr("creating", info.Source, err)
|
||||||
}
|
}
|
||||||
@ -526,7 +515,10 @@ See http://k8s.io/docs/reference/using-api/api-concepts/#conflicts`, err)
|
|||||||
fmt.Fprintf(o.ErrOut, warningNoLastAppliedConfigAnnotation, o.cmdBaseName)
|
fmt.Fprintf(o.ErrOut, warningNoLastAppliedConfigAnnotation, o.cmdBaseName)
|
||||||
}
|
}
|
||||||
|
|
||||||
patcher := newPatcher(o, info)
|
patcher, err := newPatcher(o, info)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
patchBytes, patchedObject, err := patcher.Patch(info.Object, modified, info.Source, info.Namespace, info.Name, o.ErrOut)
|
patchBytes, patchedObject, err := patcher.Patch(info.Object, modified, info.Source, info.Namespace, info.Name, o.ErrOut)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patchBytes, info), info.Source, err)
|
return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patchBytes, info), info.Source, err)
|
||||||
@ -663,42 +655,3 @@ func (o *ApplyOptions) PrintAndPrunePostProcessor() func() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 cmdutil.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 := openapi.SupportsDryRun(oapi, gvk)
|
|
||||||
if err != nil {
|
|
||||||
// We assume that we couldn't find the type, then check for namespace:
|
|
||||||
supports, _ = openapi.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
|
|
||||||
}
|
|
||||||
|
@ -29,7 +29,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/googleapis/gnostic/OpenAPIv2"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
@ -1389,75 +1388,3 @@ func TestForceApply(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDryRunVerifier(t *testing.T) {
|
|
||||||
dryRunVerifier := DryRunVerifier{
|
|
||||||
Finder: cmdutil.NewCRDFinder(func() ([]schema.GroupKind, error) {
|
|
||||||
return []schema.GroupKind{
|
|
||||||
{
|
|
||||||
Group: "crd.com",
|
|
||||||
Kind: "MyCRD",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Group: "crd.com",
|
|
||||||
Kind: "MyNewCRD",
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
OpenAPIGetter: &fakeSchema,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := dryRunVerifier.HasSupport(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "NodeProxyOptions"})
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("NodeProxyOptions doesn't support dry-run, yet no error found")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = dryRunVerifier.HasSupport(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Pod should support dry-run: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = dryRunVerifier.HasSupport(schema.GroupVersionKind{Group: "crd.com", Version: "v1", Kind: "MyCRD"})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("MyCRD should support dry-run: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = dryRunVerifier.HasSupport(schema.GroupVersionKind{Group: "crd.com", Version: "v1", Kind: "Random"})
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("Random doesn't support dry-run, yet no error found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type EmptyOpenAPI struct{}
|
|
||||||
|
|
||||||
func (EmptyOpenAPI) OpenAPISchema() (*openapi_v2.Document, error) {
|
|
||||||
return &openapi_v2.Document{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDryRunVerifierNoOpenAPI(t *testing.T) {
|
|
||||||
dryRunVerifier := DryRunVerifier{
|
|
||||||
Finder: cmdutil.NewCRDFinder(func() ([]schema.GroupKind, error) {
|
|
||||||
return []schema.GroupKind{
|
|
||||||
{
|
|
||||||
Group: "crd.com",
|
|
||||||
Kind: "MyCRD",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Group: "crd.com",
|
|
||||||
Kind: "MyNewCRD",
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
OpenAPIGetter: EmptyOpenAPI{},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := dryRunVerifier.HasSupport(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"})
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("Pod doesn't support dry-run, yet no error found")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = dryRunVerifier.HasSupport(schema.GroupVersionKind{Group: "crd.com", Version: "v1", Kind: "MyCRD"})
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("MyCRD doesn't support dry-run, yet no error found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -25,7 +25,6 @@ import (
|
|||||||
"github.com/jonboulle/clockwork"
|
"github.com/jonboulle/clockwork"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"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/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
@ -75,13 +74,19 @@ type Patcher struct {
|
|||||||
OpenapiSchema openapi.Resources
|
OpenapiSchema openapi.Resources
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPatcher(o *ApplyOptions, info *resource.Info) *Patcher {
|
func newPatcher(o *ApplyOptions, info *resource.Info) (*Patcher, error) {
|
||||||
var openapiSchema openapi.Resources
|
var openapiSchema openapi.Resources
|
||||||
if o.OpenAPIPatch {
|
if o.OpenAPIPatch {
|
||||||
openapiSchema = o.OpenAPISchema
|
openapiSchema = o.OpenAPISchema
|
||||||
}
|
}
|
||||||
|
|
||||||
helper := resource.NewHelper(info.Client, info.Mapping)
|
helper := resource.NewHelper(info.Client, info.Mapping)
|
||||||
|
if o.ServerDryRun {
|
||||||
|
if err := resource.VerifyDryRun(info.Mapping.GroupVersionKind, o.DynamicClient, o.DiscoveryClient); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
helper.DryRun(o.ServerDryRun)
|
||||||
|
}
|
||||||
return &Patcher{
|
return &Patcher{
|
||||||
Mapping: info.Mapping,
|
Mapping: info.Mapping,
|
||||||
Helper: helper,
|
Helper: helper,
|
||||||
@ -95,7 +100,7 @@ func newPatcher(o *ApplyOptions, info *resource.Info) *Patcher {
|
|||||||
ServerDryRun: o.ServerDryRun,
|
ServerDryRun: o.ServerDryRun,
|
||||||
OpenapiSchema: openapiSchema,
|
OpenapiSchema: openapiSchema,
|
||||||
Retries: maxPatchRetry,
|
Retries: maxPatchRetry,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Patcher) delete(namespace, name string) error {
|
func (p *Patcher) delete(namespace, name string) error {
|
||||||
@ -180,12 +185,7 @@ func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, source, names
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
options := metav1.PatchOptions{}
|
patchedObj, err := p.Helper.Patch(namespace, name, patchType, patch, nil)
|
||||||
if p.ServerDryRun {
|
|
||||||
options.DryRun = []string{metav1.DryRunAll}
|
|
||||||
}
|
|
||||||
|
|
||||||
patchedObj, err := p.Helper.Patch(namespace, name, patchType, patch, &options)
|
|
||||||
return patch, patchedObj, err
|
return patch, patchedObj, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,15 +230,11 @@ func (p *Patcher) deleteAndCreate(original runtime.Object, modified []byte, name
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return modified, nil, err
|
return modified, nil, err
|
||||||
}
|
}
|
||||||
options := metav1.CreateOptions{}
|
createdObject, err := p.Helper.Create(namespace, true, versionedObject)
|
||||||
if p.ServerDryRun {
|
|
||||||
options.DryRun = []string{metav1.DryRunAll}
|
|
||||||
}
|
|
||||||
createdObject, err := p.Helper.Create(namespace, true, versionedObject, &options)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// restore the original object if we fail to create the new one
|
// restore the original object if we fail to create the new one
|
||||||
// but still propagate and advertise error to user
|
// but still propagate and advertise error to user
|
||||||
recreated, recreateErr := p.Helper.Create(namespace, true, original, &options)
|
recreated, recreateErr := p.Helper.Create(namespace, true, original)
|
||||||
if recreateErr != nil {
|
if recreateErr != nil {
|
||||||
err = fmt.Errorf("An error occurred force-replacing the existing object with the newly provided one:\n\n%v.\n\nAdditionally, an error occurred attempting to restore the original object:\n\n%v", err, recreateErr)
|
err = fmt.Errorf("An error occurred force-replacing the existing object with the newly provided one:\n\n%v.\n\nAdditionally, an error occurred attempting to restore the original object:\n\n%v", err, recreateErr)
|
||||||
} else {
|
} else {
|
||||||
|
@ -299,7 +299,7 @@ func RunEditOnCreate(f cmdutil.Factory, printFlags *genericclioptions.PrintFlags
|
|||||||
|
|
||||||
// createAndRefresh creates an object from input info and refreshes info with that object
|
// createAndRefresh creates an object from input info and refreshes info with that object
|
||||||
func createAndRefresh(info *resource.Info) error {
|
func createAndRefresh(info *resource.Info) error {
|
||||||
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object, nil)
|
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ type DiffOptions struct {
|
|||||||
OpenAPISchema openapi.Resources
|
OpenAPISchema openapi.Resources
|
||||||
DiscoveryClient discovery.DiscoveryInterface
|
DiscoveryClient discovery.DiscoveryInterface
|
||||||
DynamicClient dynamic.Interface
|
DynamicClient dynamic.Interface
|
||||||
DryRunVerifier *apply.DryRunVerifier
|
DryRunVerifier *resource.DryRunVerifier
|
||||||
CmdNamespace string
|
CmdNamespace string
|
||||||
EnforceNamespace bool
|
EnforceNamespace bool
|
||||||
Builder *resource.Builder
|
Builder *resource.Builder
|
||||||
@ -295,7 +295,7 @@ func (obj InfoObject) Merged() (runtime.Object, error) {
|
|||||||
// Build the patcher, and then apply the patch with dry-run, unless the object doesn't exist, in which case we need to create it.
|
// Build the patcher, and then apply the patch with dry-run, unless the object doesn't exist, in which case we need to create it.
|
||||||
if obj.Live() == nil {
|
if obj.Live() == nil {
|
||||||
// Dry-run create if the object doesn't exist.
|
// Dry-run create if the object doesn't exist.
|
||||||
return resource.NewHelper(obj.Info.Client, obj.Info.Mapping).Create(
|
return resource.NewHelper(obj.Info.Client, obj.Info.Mapping).CreateWithOptions(
|
||||||
obj.Info.Namespace,
|
obj.Info.Namespace,
|
||||||
true,
|
true,
|
||||||
obj.LocalObj,
|
obj.LocalObj,
|
||||||
@ -427,10 +427,7 @@ func (o *DiffOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
o.DryRunVerifier = &apply.DryRunVerifier{
|
o.DryRunVerifier = resource.NewDryRunVerifier(o.DynamicClient, o.DiscoveryClient)
|
||||||
Finder: cmdutil.NewCRDFinder(cmdutil.CRDFromDynamic(o.DynamicClient)),
|
|
||||||
OpenAPIGetter: o.DiscoveryClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
o.CmdNamespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
o.CmdNamespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -355,7 +355,7 @@ func (o *ReplaceOptions) forceReplace() error {
|
|||||||
klog.V(4).Infof("error recording current command: %v", err)
|
klog.V(4).Infof("error recording current command: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object, nil)
|
obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -708,7 +708,7 @@ func (o *RunOptions) createGeneratedObject(f cmdutil.Factory, cmd *cobra.Command
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
actualObj, err = resource.NewHelper(client, mapping).Create(namespace, false, obj, nil)
|
actualObj, err = resource.NewHelper(client, mapping).Create(namespace, false, obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
|||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
"crdfinder.go",
|
|
||||||
"factory.go",
|
"factory.go",
|
||||||
"factory_client_access.go",
|
"factory_client_access.go",
|
||||||
"helpers.go",
|
"helpers.go",
|
||||||
@ -18,7 +17,6 @@ go_library(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||||
@ -48,10 +46,7 @@ go_library(
|
|||||||
|
|
||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = [
|
srcs = ["helpers_test.go"],
|
||||||
"crdfinder_test.go",
|
|
||||||
"helpers_test.go",
|
|
||||||
],
|
|
||||||
embed = [":go_default_library"],
|
embed = [":go_default_library"],
|
||||||
deps = [
|
deps = [
|
||||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||||
|
@ -4,7 +4,6 @@ go_library(
|
|||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
"doc.go",
|
"doc.go",
|
||||||
"dryrun.go",
|
|
||||||
"extensions.go",
|
"extensions.go",
|
||||||
"openapi.go",
|
"openapi.go",
|
||||||
"openapi_getter.go",
|
"openapi_getter.go",
|
||||||
@ -17,7 +16,6 @@ go_library(
|
|||||||
"//staging/src/k8s.io/client-go/discovery:go_default_library",
|
"//staging/src/k8s.io/client-go/discovery:go_default_library",
|
||||||
"//vendor/github.com/go-openapi/spec:go_default_library",
|
"//vendor/github.com/go-openapi/spec:go_default_library",
|
||||||
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
|
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
|
||||||
"//vendor/gopkg.in/yaml.v2:go_default_library",
|
|
||||||
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
|
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -25,7 +23,6 @@ go_library(
|
|||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = [
|
srcs = [
|
||||||
"dryrun_test.go",
|
|
||||||
"openapi_getter_test.go",
|
"openapi_getter_test.go",
|
||||||
"openapi_suite_test.go",
|
"openapi_suite_test.go",
|
||||||
"openapi_test.go",
|
"openapi_test.go",
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 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 openapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
|
|
||||||
yaml "gopkg.in/yaml.v2"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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")
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
/*
|
|
||||||
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 openapi_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/kubectl/pkg/util/openapi"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSupportsDryRun(t *testing.T) {
|
|
||||||
doc, err := fakeSchema.OpenAPISchema()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to get OpenAPI Schema: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
gvk schema.GroupVersionKind
|
|
||||||
success bool
|
|
||||||
supports bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
gvk: schema.GroupVersionKind{
|
|
||||||
Group: "",
|
|
||||||
Version: "v1",
|
|
||||||
Kind: "Pod",
|
|
||||||
},
|
|
||||||
success: true,
|
|
||||||
supports: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
gvk: schema.GroupVersionKind{
|
|
||||||
Group: "",
|
|
||||||
Version: "v1",
|
|
||||||
Kind: "UnknownKind",
|
|
||||||
},
|
|
||||||
success: false,
|
|
||||||
supports: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
gvk: schema.GroupVersionKind{
|
|
||||||
Group: "",
|
|
||||||
Version: "v1",
|
|
||||||
Kind: "NodeProxyOptions",
|
|
||||||
},
|
|
||||||
success: true,
|
|
||||||
supports: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
supports, err := openapi.SupportsDryRun(doc, test.gvk)
|
|
||||||
if supports != test.supports || ((err == nil) != test.success) {
|
|
||||||
errStr := "nil"
|
|
||||||
if test.success == false {
|
|
||||||
errStr = "err"
|
|
||||||
}
|
|
||||||
t.Errorf("SupportsDryRun(doc, %v) = (%v, %v), expected (%v, %v)",
|
|
||||||
test.gvk,
|
|
||||||
supports, err,
|
|
||||||
test.supports, errStr,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user