Skip to content

Commit 9f8aab6

Browse files
authored
Merge pull request #812 from alexeldeib/ace/unstruct
🐛 respect context in unstructured client
2 parents 741745a + fd9006b commit 9f8aab6

File tree

6 files changed

+236
-117
lines changed

6 files changed

+236
-117
lines changed

pkg/client/client.go

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,12 @@ package client
1919
import (
2020
"context"
2121
"fmt"
22-
"reflect"
2322

2423
"k8s.io/apimachinery/pkg/api/meta"
2524
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2625
"k8s.io/apimachinery/pkg/runtime"
2726
"k8s.io/apimachinery/pkg/runtime/schema"
2827
"k8s.io/apimachinery/pkg/runtime/serializer"
29-
"k8s.io/client-go/dynamic"
3028
"k8s.io/client-go/kubernetes/scheme"
3129
"k8s.io/client-go/rest"
3230
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
@@ -70,25 +68,22 @@ func New(config *rest.Config, options Options) (Client, error) {
7068
}
7169
}
7270

73-
dynamicClient, err := dynamic.NewForConfig(config)
74-
if err != nil {
75-
return nil, err
71+
clientcache := &clientCache{
72+
config: config,
73+
scheme: options.Scheme,
74+
mapper: options.Mapper,
75+
codecs: serializer.NewCodecFactory(options.Scheme),
76+
resourceByType: make(map[schema.GroupVersionKind]*resourceMeta),
7677
}
7778

7879
c := &client{
7980
typedClient: typedClient{
80-
cache: clientCache{
81-
config: config,
82-
scheme: options.Scheme,
83-
mapper: options.Mapper,
84-
codecs: serializer.NewCodecFactory(options.Scheme),
85-
resourceByType: make(map[reflect.Type]*resourceMeta),
86-
},
81+
cache: clientcache,
8782
paramCodec: runtime.NewParameterCodec(options.Scheme),
8883
},
8984
unstructuredClient: unstructuredClient{
90-
client: dynamicClient,
91-
restMapper: options.Mapper,
85+
cache: clientcache,
86+
paramCodec: noConversionParamCodec{},
9287
},
9388
}
9489

pkg/client/client_cache.go

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ limitations under the License.
1717
package client
1818

1919
import (
20-
"reflect"
2120
"strings"
2221
"sync"
2322

@@ -45,19 +44,14 @@ type clientCache struct {
4544
codecs serializer.CodecFactory
4645

4746
// resourceByType caches type metadata
48-
resourceByType map[reflect.Type]*resourceMeta
47+
resourceByType map[schema.GroupVersionKind]*resourceMeta
4948
mu sync.RWMutex
5049
}
5150

5251
// newResource maps obj to a Kubernetes Resource and constructs a client for that Resource.
5352
// If the object is a list, the resource represents the item's type instead.
54-
func (c *clientCache) newResource(obj runtime.Object) (*resourceMeta, error) {
55-
gvk, err := apiutil.GVKForObject(obj, c.scheme)
56-
if err != nil {
57-
return nil, err
58-
}
59-
60-
if strings.HasSuffix(gvk.Kind, "List") && meta.IsListType(obj) {
53+
func (c *clientCache) newResource(gvk schema.GroupVersionKind, isList bool) (*resourceMeta, error) {
54+
if strings.HasSuffix(gvk.Kind, "List") && isList {
6155
// if this was a list, treat it as a request for the item's resource
6256
gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
6357
}
@@ -76,12 +70,15 @@ func (c *clientCache) newResource(obj runtime.Object) (*resourceMeta, error) {
7670
// getResource returns the resource meta information for the given type of object.
7771
// If the object is a list, the resource represents the item's type instead.
7872
func (c *clientCache) getResource(obj runtime.Object) (*resourceMeta, error) {
79-
typ := reflect.TypeOf(obj)
73+
gvk, err := apiutil.GVKForObject(obj, c.scheme)
74+
if err != nil {
75+
return nil, err
76+
}
8077

8178
// It's better to do creation work twice than to not let multiple
8279
// people make requests at once
8380
c.mu.RLock()
84-
r, known := c.resourceByType[typ]
81+
r, known := c.resourceByType[gvk]
8582
c.mu.RUnlock()
8683

8784
if known {
@@ -91,11 +88,11 @@ func (c *clientCache) getResource(obj runtime.Object) (*resourceMeta, error) {
9188
// Initialize a new Client
9289
c.mu.Lock()
9390
defer c.mu.Unlock()
94-
r, err := c.newResource(obj)
91+
r, err = c.newResource(gvk, meta.IsListType(obj))
9592
if err != nil {
9693
return nil, err
9794
}
98-
c.resourceByType[typ] = r
95+
c.resourceByType[gvk] = r
9996
return r, err
10097
}
10198

pkg/client/client_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,57 @@ var _ = Describe("Client", func() {
827827
close(done)
828828
})
829829

830+
It("should update status and preserve type information", func(done Done) {
831+
cl, err := client.New(cfg, client.Options{})
832+
Expect(err).NotTo(HaveOccurred())
833+
Expect(cl).NotTo(BeNil())
834+
835+
By("initially creating a Deployment")
836+
dep, err := clientset.AppsV1().Deployments(ns).Create(dep)
837+
Expect(err).NotTo(HaveOccurred())
838+
839+
By("updating the status of Deployment")
840+
u := &unstructured.Unstructured{}
841+
dep.Status.Replicas = 1
842+
Expect(scheme.Convert(dep, u, nil)).To(Succeed())
843+
err = cl.Status().Update(context.TODO(), u)
844+
Expect(err).NotTo(HaveOccurred())
845+
846+
By("validating updated Deployment has type information")
847+
Expect(u.GroupVersionKind()).To(Equal(depGvk))
848+
849+
close(done)
850+
})
851+
852+
It("should patch status and preserve type information", func(done Done) {
853+
cl, err := client.New(cfg, client.Options{})
854+
Expect(err).NotTo(HaveOccurred())
855+
Expect(cl).NotTo(BeNil())
856+
857+
By("initially creating a Deployment")
858+
dep, err := clientset.AppsV1().Deployments(ns).Create(dep)
859+
Expect(err).NotTo(HaveOccurred())
860+
861+
By("patching the status of Deployment")
862+
u := &unstructured.Unstructured{}
863+
depPatch := client.MergeFrom(dep.DeepCopy())
864+
dep.Status.Replicas = 1
865+
Expect(scheme.Convert(dep, u, nil)).To(Succeed())
866+
err = cl.Status().Patch(context.TODO(), u, depPatch)
867+
Expect(err).NotTo(HaveOccurred())
868+
869+
By("validating updated Deployment has type information")
870+
Expect(u.GroupVersionKind()).To(Equal(depGvk))
871+
872+
By("validating patched Deployment has new status")
873+
actual, err := clientset.AppsV1().Deployments(ns).Get(dep.Name, metav1.GetOptions{})
874+
Expect(err).NotTo(HaveOccurred())
875+
Expect(actual).NotTo(BeNil())
876+
Expect(actual.Status.Replicas).To(BeEquivalentTo(1))
877+
878+
close(done)
879+
})
880+
830881
It("should not update spec of an existing object", func(done Done) {
831882
cl, err := client.New(cfg, client.Options{})
832883
Expect(err).NotTo(HaveOccurred())

pkg/client/codec.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package client
2+
3+
import (
4+
"errors"
5+
"net/url"
6+
7+
"k8s.io/apimachinery/pkg/conversion/queryparams"
8+
"k8s.io/apimachinery/pkg/runtime"
9+
"k8s.io/apimachinery/pkg/runtime/schema"
10+
)
11+
12+
var _ runtime.ParameterCodec = noConversionParamCodec{}
13+
14+
// noConversionParamCodec is a no-conversion codec for serializing parameters into URL query strings.
15+
// it's useful in scenarios with the unstructured client and arbitrary resouces.
16+
type noConversionParamCodec struct{}
17+
18+
func (noConversionParamCodec) EncodeParameters(obj runtime.Object, to schema.GroupVersion) (url.Values, error) {
19+
return queryparams.Convert(obj)
20+
}
21+
22+
func (noConversionParamCodec) DecodeParameters(parameters url.Values, from schema.GroupVersion, into runtime.Object) error {
23+
return errors.New("DecodeParameters not implemented on noConversionParamCodec")
24+
}

pkg/client/typed_client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525
// client is a client.Client that reads and writes directly from/to an API server. It lazily initializes
2626
// new clients at the time they are used, and caches the client.
2727
type typedClient struct {
28-
cache clientCache
28+
cache *clientCache
2929
paramCodec runtime.ParameterCodec
3030
}
3131

0 commit comments

Comments
 (0)