Skip to content

Commit 86f2cee

Browse files
authored
Merge pull request #22 from jaypipes/same-name-kind
fix inability to reference same-named Kinds
2 parents ecc124b + 20bf151 commit 86f2cee

8 files changed

+295
-114
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![Go Reference](https://pkg.go.dev/badge/github.com/gdt-dev/kube.svg)](https://pkg.go.dev/github.com/gdt-dev/kube)
44
[![Go Report Card](https://goreportcard.com/badge/github.com/gdt-dev/kube)](https://goreportcard.com/report/github.com/gdt-dev/kube)
5-
[![Build Status](https://github.com/gdt-dev/kube/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/gdt-dev/kube/actions)
5+
[![Build Status](https://github.com/gdt-dev/kube/actions/workflows/gate-tests.yml/badge.svg?branch=main)](https://github.com/gdt-dev/kube/actions)
66
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md)
77

88
<div style="float: left">

action.go

+86-45
Original file line numberDiff line numberDiff line change
@@ -121,14 +121,11 @@ func (a *Action) get(
121121
ns string,
122122
out *interface{},
123123
) error {
124-
kind, name := a.Get.KindName()
125-
gvk := schema.GroupVersionKind{
126-
Kind: kind,
127-
}
128-
res, err := c.gvrFromGVK(gvk)
124+
res, err := c.gvrFromArg(a.Get.Arg)
129125
if err != nil {
130126
return err
131127
}
128+
name := a.Get.Name
132129
if name == "" {
133130
list, err := a.doList(ctx, c, res, ns)
134131
if err == nil {
@@ -154,7 +151,7 @@ func (a *Action) doList(
154151
resName := res.Resource
155152
labelSelString := ""
156153
opts := metav1.ListOptions{}
157-
withlabels := a.Get.Labels()
154+
withlabels := a.Get.Labels
158155
if withlabels != nil {
159156
// We already validated the label selector during parse-time
160157
labelsStr := labels.Set(withlabels).String()
@@ -250,21 +247,30 @@ func (a *Action) create(
250247
}
251248
for _, obj := range objs {
252249
gvk := obj.GetObjectKind().GroupVersionKind()
253-
ons := obj.GetNamespace()
254-
if ons == "" {
255-
ons = ns
256-
}
257250
res, err := c.gvrFromGVK(gvk)
258251
if err != nil {
259252
return err
260253
}
261254
resName := res.Resource
262-
debug.Println(ctx, "kube.create: %s (ns: %s)", resName, ons)
263-
obj, err := c.client.Resource(res).Namespace(ons).Create(
264-
ctx,
265-
obj,
266-
metav1.CreateOptions{},
267-
)
255+
if c.resourceNamespaced(res) {
256+
ons := obj.GetNamespace()
257+
if ons == "" {
258+
ons = ns
259+
}
260+
debug.Println(ctx, "kube.create: %s (ns: %s)", resName, ons)
261+
obj, err = c.client.Resource(res).Namespace(ons).Create(
262+
ctx,
263+
obj,
264+
metav1.CreateOptions{},
265+
)
266+
} else {
267+
debug.Println(ctx, "kube.create: %s (non-namespaced resource)", resName)
268+
obj, err = c.client.Resource(res).Create(
269+
ctx,
270+
obj,
271+
metav1.CreateOptions{},
272+
)
273+
}
268274
if err != nil {
269275
return err
270276
}
@@ -314,28 +320,44 @@ func (a *Action) apply(
314320
}
315321
for _, obj := range objs {
316322
gvk := obj.GetObjectKind().GroupVersionKind()
317-
ons := obj.GetNamespace()
318-
if ons == "" {
319-
ons = ns
320-
}
321323
res, err := c.gvrFromGVK(gvk)
322324
if err != nil {
323325
return err
324326
}
325327
resName := res.Resource
326-
debug.Println(ctx, "kube.apply: %s (ns: %s)", resName, ons)
327-
obj, err := c.client.Resource(res).Namespace(ns).Apply(
328-
ctx,
329-
// NOTE(jaypipes): Not sure why a separate name argument is
330-
// necessary considering `obj` is of type
331-
// `*unstructured.Unstructured` and therefore has the `GetName()`
332-
// method...
333-
obj.GetName(),
334-
obj,
335-
// TODO(jaypipes): Not sure if this hard-coded options struct is
336-
// always going to work. Maybe add ability to control it?
337-
metav1.ApplyOptions{FieldManager: fieldManagerName, Force: true},
338-
)
328+
if c.resourceNamespaced(res) {
329+
ons := obj.GetNamespace()
330+
if ons == "" {
331+
ons = ns
332+
}
333+
debug.Println(ctx, "kube.apply: %s (ns: %s)", resName, ons)
334+
obj, err = c.client.Resource(res).Namespace(ns).Apply(
335+
ctx,
336+
// NOTE(jaypipes): Not sure why a separate name argument is
337+
// necessary considering `obj` is of type
338+
// `*unstructured.Unstructured` and therefore has the `GetName()`
339+
// method...
340+
obj.GetName(),
341+
obj,
342+
// TODO(jaypipes): Not sure if this hard-coded options struct is
343+
// always going to work. Maybe add ability to control it?
344+
metav1.ApplyOptions{FieldManager: fieldManagerName, Force: true},
345+
)
346+
} else {
347+
debug.Println(ctx, "kube.apply: %s (non-namespaced resource)", resName)
348+
obj, err = c.client.Resource(res).Apply(
349+
ctx,
350+
// NOTE(jaypipes): Not sure why a separate name argument is
351+
// necessary considering `obj` is of type
352+
// `*unstructured.Unstructured` and therefore has the `GetName()`
353+
// method...
354+
obj.GetName(),
355+
obj,
356+
// TODO(jaypipes): Not sure if this hard-coded options struct is
357+
// always going to work. Maybe add ability to control it?
358+
metav1.ApplyOptions{FieldManager: fieldManagerName, Force: true},
359+
)
360+
}
339361
if err != nil {
340362
return err
341363
}
@@ -385,14 +407,11 @@ func (a *Action) delete(
385407
return nil
386408
}
387409

388-
kind, name := a.Delete.KindName()
389-
gvk := schema.GroupVersionKind{
390-
Kind: kind,
391-
}
392-
res, err := c.gvrFromGVK(gvk)
410+
res, err := c.gvrFromArg(a.Delete.Arg)
393411
if err != nil {
394412
return err
395413
}
414+
name := a.Delete.Name
396415
if name == "" {
397416
return a.doDeleteCollection(ctx, c, res, ns)
398417
}
@@ -408,11 +427,22 @@ func (a *Action) doDelete(
408427
name string,
409428
) error {
410429
resName := res.Resource
430+
if c.resourceNamespaced(res) {
431+
debug.Println(
432+
ctx, "kube.delete: %s/%s (ns: %s)",
433+
resName, name, ns,
434+
)
435+
return c.client.Resource(res).Namespace(ns).Delete(
436+
ctx,
437+
name,
438+
metav1.DeleteOptions{},
439+
)
440+
}
411441
debug.Println(
412-
ctx, "kube.delete: %s/%s (ns: %s)",
413-
resName, name, ns,
442+
ctx, "kube.delete: %s/%s (non-namespaced resource)",
443+
resName, name,
414444
)
415-
return c.client.Resource(res).Namespace(ns).Delete(
445+
return c.client.Resource(res).Delete(
416446
ctx,
417447
name,
418448
metav1.DeleteOptions{},
@@ -428,7 +458,7 @@ func (a *Action) doDeleteCollection(
428458
ns string,
429459
) error {
430460
opts := metav1.ListOptions{}
431-
withlabels := a.Delete.Labels()
461+
withlabels := a.Delete.Labels
432462
labelSelString := ""
433463
if withlabels != nil {
434464
// We already validated the label selector during parse-time
@@ -437,11 +467,22 @@ func (a *Action) doDeleteCollection(
437467
opts.LabelSelector = labelsStr
438468
}
439469
resName := res.Resource
470+
if c.resourceNamespaced(res) {
471+
debug.Println(
472+
ctx, "kube.delete: %s%s (ns: %s)",
473+
resName, labelSelString, ns,
474+
)
475+
return c.client.Resource(res).Namespace(ns).DeleteCollection(
476+
ctx,
477+
metav1.DeleteOptions{},
478+
opts,
479+
)
480+
}
440481
debug.Println(
441-
ctx, "kube.delete: %s%s (ns: %s)",
442-
resName, labelSelString, ns,
482+
ctx, "kube.delete: %s%s (non-namespaced resource)",
483+
resName, labelSelString,
443484
)
444-
return c.client.Resource(res).Namespace(ns).DeleteCollection(
485+
return c.client.Resource(res).DeleteCollection(
445486
ctx,
446487
metav1.DeleteOptions{},
447488
opts,

connect.go

+43-9
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,27 @@ type connection struct {
9797
client dynamic.Interface
9898
}
9999

100-
// mappingFor returns a RESTMapper for a given resource type or kind
101-
func (c *connection) mappingFor(typeOrKind string) (*meta.RESTMapping, error) {
102-
fullySpecifiedGVR, groupResource := schema.ParseResourceArg(typeOrKind)
100+
// mappingForGVK returns a RESTMapper for a given GroupVersionKind
101+
func (c *connection) mappingForGVK(gvk schema.GroupVersionKind) (*meta.RESTMapping, error) {
102+
mapping, err := c.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
103+
if err != nil {
104+
// if we error out here, it is because we could not match a resource or a kind
105+
// for the given argument. To maintain consistency with previous behavior,
106+
// announce that a resource type could not be found.
107+
// if the error is _not_ a *meta.NoKindMatchError, then we had trouble doing discovery,
108+
// so we should return the original error since it may help a user diagnose what is actually wrong
109+
if meta.IsNoMatchError(err) {
110+
return nil, fmt.Errorf("the server doesn't have a resource type %q", gvk)
111+
}
112+
return nil, err
113+
}
114+
115+
return mapping, nil
116+
}
117+
118+
// mappingForArg returns a RESTMapper for a given GroupVersionKind
119+
func (c *connection) mappingForArg(arg string) (*meta.RESTMapping, error) {
120+
fullySpecifiedGVR, groupResource := schema.ParseResourceArg(arg)
103121
gvk := schema.GroupVersionKind{}
104122

105123
if fullySpecifiedGVR != nil {
@@ -112,7 +130,7 @@ func (c *connection) mappingFor(typeOrKind string) (*meta.RESTMapping, error) {
112130
return c.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
113131
}
114132

115-
fullySpecifiedGVK, groupKind := schema.ParseKindArg(typeOrKind)
133+
fullySpecifiedGVK, groupKind := schema.ParseKindArg(arg)
116134
if fullySpecifiedGVK == nil {
117135
gvk := groupKind.WithVersion("")
118136
fullySpecifiedGVK = &gvk
@@ -140,18 +158,34 @@ func (c *connection) mappingFor(typeOrKind string) (*meta.RESTMapping, error) {
140158
return mapping, nil
141159
}
142160

161+
// gvrFromArg returns a GroupVersionResource from a resource or kind arg
162+
// string, using the discovery client to look up the resource name (the plural
163+
// of the kind). The returned GroupVersionResource will have the proper Group
164+
// and Version filled in (as opposed to an APIResource which has empty Group
165+
// and Version strings because it "inherits" its APIResourceList's GroupVersion
166+
// ... ugh.)
167+
func (c *connection) gvrFromArg(
168+
arg string,
169+
) (schema.GroupVersionResource, error) {
170+
empty := schema.GroupVersionResource{}
171+
r, err := c.mappingForArg(arg)
172+
if err != nil {
173+
return empty, ResourceUnknown(arg)
174+
}
175+
176+
return r.Resource, nil
177+
}
178+
143179
// gvrFromGVK returns a GroupVersionResource from a GroupVersionKind, using the
144180
// discovery client to look up the resource name (the plural of the kind). The
145181
// returned GroupVersionResource will have the proper Group and Version filled
146182
// in (as opposed to an APIResource which has empty Group and Version strings
147183
// because it "inherits" its APIResourceList's GroupVersion ... ugh.)
148-
func (c *connection) gvrFromGVK(
149-
gvk schema.GroupVersionKind,
150-
) (schema.GroupVersionResource, error) {
184+
func (c *connection) gvrFromGVK(gvk schema.GroupVersionKind) (schema.GroupVersionResource, error) {
151185
empty := schema.GroupVersionResource{}
152-
r, err := c.mappingFor(gvk.Kind)
186+
r, err := c.mappingForGVK(gvk)
153187
if err != nil {
154-
return empty, ResourceUnknown(gvk)
188+
return empty, ResourceUnknown(gvk.String())
155189
}
156190

157191
return r.Resource, nil

errors.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99

1010
"github.com/gdt-dev/gdt/api"
1111
"gopkg.in/yaml.v3"
12-
"k8s.io/apimachinery/pkg/runtime/schema"
1312
)
1413

1514
var (
@@ -181,9 +180,10 @@ func InvalidWithLabels(err error, node *yaml.Node) error {
181180
)
182181
}
183182

184-
// ResourceUnknown returns ErrRuntimeResourceUnknown for a given kind
185-
func ResourceUnknown(gvk schema.GroupVersionKind) error {
186-
return fmt.Errorf("%w: %s", ErrResourceUnknown, gvk)
183+
// ResourceUnknown returns ErrRuntimeResourceUnknown for a given resource or
184+
// kind arg string
185+
func ResourceUnknown(arg string) error {
186+
return fmt.Errorf("%w: %s", ErrResourceUnknown, arg)
187187
}
188188

189189
// ExpectedNotFound returns ErrExpectedNotFound for a given status code or

eval_test.go

+17
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,23 @@ func TestCreateUnknownResource(t *testing.T) {
6767
require.Nil(err)
6868
}
6969

70+
func TestSameNamedKind(t *testing.T) {
71+
testutil.SkipIfNoKind(t)
72+
require := require.New(t)
73+
74+
fp := filepath.Join("testdata", "same-named-kind.yaml")
75+
76+
s, err := gdt.From(fp)
77+
require.Nil(err)
78+
require.NotNil(s)
79+
80+
ctx := gdtcontext.New()
81+
ctx = gdtcontext.RegisterFixture(ctx, "kind", kindfix.New())
82+
83+
err = s.Run(ctx, t)
84+
require.Nil(err)
85+
}
86+
7087
func TestDeleteResourceNotFound(t *testing.T) {
7188
testutil.SkipIfNoKind(t)
7289
require := require.New(t)

0 commit comments

Comments
 (0)