Skip to content

Commit c014515

Browse files
authored
Merge pull request kubernetes#86408 from julianvmodesto/kubectl-ss-dry-run-helper
Support server-side dry-run in cli-runtime REST Helper
2 parents 84beab6 + 6bea0e4 commit c014515

File tree

23 files changed

+110857
-342
lines changed

23 files changed

+110857
-342
lines changed

staging/src/k8s.io/cli-runtime/artifacts/openapi/swagger.json

Lines changed: 110465 additions & 0 deletions
Large diffs are not rendered by default.

staging/src/k8s.io/cli-runtime/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,19 @@ require (
1111
github.com/ghodss/yaml v1.0.0 // indirect
1212
github.com/go-openapi/jsonreference v0.19.3 // indirect
1313
github.com/go-openapi/spec v0.19.3 // indirect
14+
github.com/googleapis/gnostic v0.1.0
1415
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de
1516
github.com/mailru/easyjson v0.7.0 // indirect
1617
github.com/pkg/errors v0.8.1
1718
github.com/spf13/cobra v0.0.5
1819
github.com/spf13/pflag v1.0.5
1920
github.com/stretchr/testify v1.4.0
2021
golang.org/x/text v0.3.2
22+
gopkg.in/yaml.v2 v2.2.7
2123
k8s.io/api v0.0.0
2224
k8s.io/apimachinery v0.0.0
2325
k8s.io/client-go v0.0.0
26+
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a
2427
sigs.k8s.io/kustomize v2.0.3+incompatible
2528
sigs.k8s.io/yaml v1.1.0
2629
)

staging/src/k8s.io/cli-runtime/go.sum

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

staging/src/k8s.io/cli-runtime/pkg/resource/BUILD

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ go_library(
55
srcs = [
66
"builder.go",
77
"client.go",
8+
"crd_finder.go",
89
"doc.go",
10+
"dry_run_verifier.go",
911
"fake.go",
1012
"helper.go",
1113
"interfaces.go",
@@ -39,11 +41,14 @@ go_library(
3941
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
4042
"//staging/src/k8s.io/cli-runtime/pkg/kustomize:go_default_library",
4143
"//staging/src/k8s.io/client-go/discovery:go_default_library",
44+
"//staging/src/k8s.io/client-go/dynamic:go_default_library",
4245
"//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library",
4346
"//staging/src/k8s.io/client-go/rest:go_default_library",
4447
"//staging/src/k8s.io/client-go/restmapper:go_default_library",
48+
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
4549
"//vendor/golang.org/x/text/encoding/unicode:go_default_library",
4650
"//vendor/golang.org/x/text/transform:go_default_library",
51+
"//vendor/gopkg.in/yaml.v2:go_default_library",
4752
"//vendor/sigs.k8s.io/kustomize/pkg/fs:go_default_library",
4853
],
4954
)
@@ -52,6 +57,8 @@ go_test(
5257
name = "go_default_test",
5358
srcs = [
5459
"builder_test.go",
60+
"crd_finder_test.go",
61+
"dry_run_verifier_test.go",
5562
"helper_test.go",
5663
"scheme_test.go",
5764
"visitor_test.go",
@@ -81,7 +88,9 @@ go_test(
8188
"//staging/src/k8s.io/client-go/restmapper:go_default_library",
8289
"//staging/src/k8s.io/client-go/util/testing:go_default_library",
8390
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
91+
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
8492
"//vendor/github.com/stretchr/testify/assert:go_default_library",
93+
"//vendor/k8s.io/kube-openapi/pkg/util/proto/testing:go_default_library",
8594
"//vendor/sigs.k8s.io/yaml:go_default_library",
8695
],
8796
)

staging/src/k8s.io/kubectl/pkg/cmd/util/crdfinder.go renamed to staging/src/k8s.io/cli-runtime/pkg/resource/crd_finder.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package util
17+
package resource
1818

1919
import (
2020
"fmt"

staging/src/k8s.io/kubectl/pkg/cmd/util/crdfinder_test.go renamed to staging/src/k8s.io/cli-runtime/pkg/resource/crd_finder_test.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,13 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package util_test
17+
package resource
1818

1919
import (
2020
"errors"
2121
"testing"
2222

2323
"k8s.io/apimachinery/pkg/runtime/schema"
24-
"k8s.io/kubectl/pkg/cmd/util"
2524
)
2625

2726
func TestCacheCRDFinder(t *testing.T) {
@@ -30,7 +29,7 @@ func TestCacheCRDFinder(t *testing.T) {
3029
called += 1
3130
return nil, nil
3231
}
33-
finder := util.NewCRDFinder(getter)
32+
finder := NewCRDFinder(getter)
3433
if called != 0 {
3534
t.Fatalf("Creating the finder shouldn't call the getter, has called = %v", called)
3635
}
@@ -55,7 +54,7 @@ func TestCRDFinderErrors(t *testing.T) {
5554
getter := func() ([]schema.GroupKind, error) {
5655
return nil, errors.New("not working")
5756
}
58-
finder := util.NewCRDFinder(getter)
57+
finder := NewCRDFinder(getter)
5958
found, err := finder.HasCRD(schema.GroupKind{Group: "", Kind: "Pod"})
6059
if found == true {
6160
t.Fatalf("Found the CRD with non-working getter function")
@@ -78,7 +77,7 @@ func TestCRDFinder(t *testing.T) {
7877
},
7978
}, nil
8079
}
81-
finder := util.NewCRDFinder(getter)
80+
finder := NewCRDFinder(getter)
8281

8382
if found, _ := finder.HasCRD(schema.GroupKind{Group: "crd.com", Kind: "MyCRD"}); !found {
8483
t.Fatalf("Failed to find CRD MyCRD")
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
Copyright 2019 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package resource
18+
19+
import (
20+
"errors"
21+
"fmt"
22+
23+
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
24+
yaml "gopkg.in/yaml.v2"
25+
"k8s.io/apimachinery/pkg/runtime/schema"
26+
"k8s.io/client-go/discovery"
27+
"k8s.io/client-go/dynamic"
28+
)
29+
30+
// VerifyDryRun returns nil if a resource group-version-kind supports
31+
// server-side dry-run. Otherwise, an error is returned.
32+
func VerifyDryRun(gvk schema.GroupVersionKind, dynamicClient dynamic.Interface, discoveryClient discovery.DiscoveryInterface) error {
33+
verifier := NewDryRunVerifier(dynamicClient, discoveryClient)
34+
return verifier.HasSupport(gvk)
35+
}
36+
37+
func NewDryRunVerifier(dynamicClient dynamic.Interface, discoveryClient discovery.DiscoveryInterface) *DryRunVerifier {
38+
return &DryRunVerifier{
39+
Finder: NewCRDFinder(CRDFromDynamic(dynamicClient)),
40+
OpenAPIGetter: discoveryClient,
41+
}
42+
}
43+
44+
func hasGVKExtension(extensions []*openapi_v2.NamedAny, gvk schema.GroupVersionKind) bool {
45+
for _, extension := range extensions {
46+
if extension.GetValue().GetYaml() == "" ||
47+
extension.GetName() != "x-kubernetes-group-version-kind" {
48+
continue
49+
}
50+
var value map[string]string
51+
err := yaml.Unmarshal([]byte(extension.GetValue().GetYaml()), &value)
52+
if err != nil {
53+
continue
54+
}
55+
56+
if value["group"] == gvk.Group && value["kind"] == gvk.Kind && value["version"] == gvk.Version {
57+
return true
58+
}
59+
return false
60+
}
61+
return false
62+
}
63+
64+
// DryRunVerifier verifies if a given group-version-kind supports DryRun
65+
// against the current server. Sending dryRun requests to apiserver that
66+
// don't support it will result in objects being unwillingly persisted.
67+
//
68+
// It reads the OpenAPI to see if the given GVK supports dryRun. If the
69+
// GVK can not be found, we assume that CRDs will have the same level of
70+
// support as "namespaces", and non-CRDs will not be supported. We
71+
// delay the check for CRDs as much as possible though, since it
72+
// requires an extra round-trip to the server.
73+
type DryRunVerifier struct {
74+
Finder CRDFinder
75+
OpenAPIGetter discovery.OpenAPISchemaInterface
76+
}
77+
78+
// HasSupport verifies if the given gvk supports DryRun. An error is
79+
// returned if it doesn't.
80+
func (v *DryRunVerifier) HasSupport(gvk schema.GroupVersionKind) error {
81+
oapi, err := v.OpenAPIGetter.OpenAPISchema()
82+
if err != nil {
83+
return fmt.Errorf("failed to download openapi: %v", err)
84+
}
85+
supports, err := SupportsDryRun(oapi, gvk)
86+
if err != nil {
87+
// We assume that we couldn't find the type, then check for namespace:
88+
supports, _ = SupportsDryRun(oapi, schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"})
89+
// If namespace supports dryRun, then we will support dryRun for CRDs only.
90+
if supports {
91+
supports, err = v.Finder.HasCRD(gvk.GroupKind())
92+
if err != nil {
93+
return fmt.Errorf("failed to check CRD: %v", err)
94+
}
95+
}
96+
}
97+
if !supports {
98+
return fmt.Errorf("%v doesn't support dry-run", gvk)
99+
}
100+
return nil
101+
}
102+
103+
// SupportsDryRun is a method that let's us look in the OpenAPI if the
104+
// specific group-version-kind supports the dryRun query parameter for
105+
// the PATCH end-point.
106+
func SupportsDryRun(doc *openapi_v2.Document, gvk schema.GroupVersionKind) (bool, error) {
107+
for _, path := range doc.GetPaths().GetPath() {
108+
// Is this describing the gvk we're looking for?
109+
if !hasGVKExtension(path.GetValue().GetPatch().GetVendorExtension(), gvk) {
110+
continue
111+
}
112+
for _, param := range path.GetValue().GetPatch().GetParameters() {
113+
if param.GetParameter().GetNonBodyParameter().GetQueryParameterSubSchema().GetName() == "dryRun" {
114+
return true, nil
115+
}
116+
}
117+
return false, nil
118+
}
119+
120+
return false, errors.New("couldn't find GVK in openapi")
121+
}

0 commit comments

Comments
 (0)