-
Notifications
You must be signed in to change notification settings - Fork 169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support nested lists when discovering manifests #166
Conversation
This commit modifies the generation of kubernetes manifests so that we can support lists of resources, at arbitrary levels of nesting. As an example, where before the following jsonnet would fail: ```json { resources: [ { apiVersion: "v1", kind: "ConfigMap", metadata: { name: "config", }, } ], } ``` We will now return a valid configmap resource.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for picking this up! Did a first pass, commented on everything that caught my eye.
If I were too critical somewhere, just add a comment below and we can discuss 😄
Most of this is related to refactoring mixed in a feature PR, which I am not a big fan of because it makes it harder to review. In general, we always try to touch as few lines as possible when implementing a new feature, without refactoring anything if it works without.
Once merged, we can always come back and make the code look as clean and cool as possible :D
The branch so far contains many good refactorings, however they conflate with the purpose of the branch. Most of the changes will be included in a further changeset to clean up the implementation.
The comment for ``walkJSON`` was incomplete. A more complete comment has been added
The previous logic resulted in an inability to pass objects as they were typed as ``[]interface{}``, although they were ``[]map[string]interface{}``-able. This fixes that, and takes in all values as ``[]interface{}``, then asserts the type, throwing ``ErrorPrimitiveReached`` if the type is not a ``[]map[string]interface{}``.
Thanks @lawrencejones @dyson @jackatbancast for your efforts! This already looks really good, found some nits (tests, etc). After these are addressed nothing prevents this anymore to be merged :D (finally) |
Co-Authored-By: sh0rez <[email protected]>
By removing ``walkList``, and handling the recursion in-place in ``walkJSON``, we now handle multiple list types, rather than just checking for ``[]map[string]interface{}``.
This change brings in the ``reflect`` module, which appears to be needed to supplement the lack of generics, or advanced handling in Go's type switches. This is unfortunate as the functionality inside of the ``reflect`` modules often slows down the runtime considerably, however a similar technique is used inside of ``kubectl``, among other CLIs. It should probably be avoided if attempting to serve lots of clients in an available web API, however the slowdown should be relatively minor in typical usage of tanka. Great effort has been taken to keep the changes in the code to as low a level as possible, whilst retaining as familiar structure as possible.
@sh0rez I've added support for an indeterminate depth of arrays as suggested, as well as creating a duplicate of As mentioned in my commit messages, I've brought in the We should still get around to refactoring some of the code a la @dyson's changes, but for now I think this may be the smallest set of changes I could think of to add the functionality we wanted, with a suitable test. Let me know if there are any more changes, happy to work on getting this in ASAP. |
Thanks so much, this looks very good! I'd suggest a slight refactoring of From 045999cb169af3e3e4a690efa04b039e5fad44fc Mon Sep 17 00:00:00 2001
From: sh0rez <[email protected]>
Date: Sun, 19 Jan 2020 12:18:13 +0100
Subject: [PATCH] refactor(kubernetes): separate concerns of walkJSON and
tryCoerceSlice
---
pkg/kubernetes/reconcile.go | 51 ++++++++++++++++++++-----------------
1 file changed, 27 insertions(+), 24 deletions(-)
diff --git a/pkg/kubernetes/reconcile.go b/pkg/kubernetes/reconcile.go
index 729fca9..fee55bc 100644
--- a/pkg/kubernetes/reconcile.go
+++ b/pkg/kubernetes/reconcile.go
@@ -71,21 +71,13 @@ func extract(deep interface{}) (map[string]manifest.Manifest, error) {
return extracted, nil
}
-// tryCoerceSlice will test that an input is an array or a slice,
-// and then construct a slice of empty interface.
-//
-// This is useful to handle recursion in walkJSON.
-// Types not specified exactly in a type switch (such as highly nested arrays),
-// will not be matched.
-func tryCoerceSlice(input interface{}, path trace) ([]interface{}, error) {
+// tryCoerceSlice attempts to construct a []interface{} from any other kind of
+// array/slice.
+// If the argument is not a list at all, ok will be false
+func tryCoerceSlice(input interface{}, path trace) (list []interface{}, ok bool) {
v := reflect.ValueOf(input)
- v.Kind()
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
- return nil, ErrorPrimitiveReached{
- path: path.Base(),
- key: path.Name(),
- primitive: input,
- }
+ return nil, false
}
l := v.Len()
@@ -95,7 +87,7 @@ func tryCoerceSlice(input interface{}, path trace) ([]interface{}, error) {
s[i] = v.Index(i).Interface()
}
- return s, nil
+ return s, true
}
// walkJSON recurses into either a map or list, returning a list of all objects that look
@@ -106,24 +98,35 @@ func tryCoerceSlice(input interface{}, path trace) ([]interface{}, error) {
// walkJSON, and then walkObj/walkList to handle the two different types of collection we
// support.
func walkJSON(ptr interface{}, extracted map[string]manifest.Manifest, path trace) error {
+ // check for known types
switch v := ptr.(type) {
case map[string]interface{}:
return walkObj(v, extracted, path)
- case []interface{}: // Duplicated to handle multiple list types
- for idx, value := range v {
- err := walkJSON(value, extracted, append(path, fmt.Sprintf("[%d]", idx)))
- if err != nil {
- return err
- }
+ case []interface{}:
+ return walkList(v, extracted, path)
+ }
+
+ // Lists other than []interface{} need to be handled separately
+ s, ok := tryCoerceSlice(ptr, path)
+ if !ok {
+ // object and list tried, so not a collection.
+ return ErrorPrimitiveReached{
+ path: path.Base(),
+ key: path.Name(),
+ primitive: ptr,
}
- return nil
- default:
- s, err := tryCoerceSlice(ptr, path)
+ }
+ return walkJSON(s, extracted, path)
+}
+
+func walkList(list []interface{}, extracted map[string]manifest.Manifest, path trace) error {
+ for idx, value := range list {
+ err := walkJSON(value, extracted, append(path, fmt.Sprintf("[%d]", idx)))
if err != nil {
return err
}
- return walkJSON(s, extracted, path)
}
+ return nil
}
func walkObj(obj objx.Map, extracted map[string]manifest.Manifest, path trace) error {
--
2.24.1 |
Also, could |
d68d61e
to
56577f2
Compare
This returns the error to ``walkJSON``, avoiding conflation with ``tryCoerceSlice``. ``tryCoerceSlice`` is now also below ``walkObj`` and ``walkList``, so that the functions are defined in the order in which they are used. Thanks for the patch @sh0rez
56577f2
to
1ed1477
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, this turned out awesome 🚀
Resolves feedback in #122
This commit modifies the generation of kubernetes manifests so that we
can support lists of resources, at arbitrary levels of nesting. As an
example, where before the following jsonnet would fail:
We will now return a valid configmap resource.