-
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
feat: inline Environment #403
Conversation
06a5a04
to
0124866
Compare
rebased. |
0124866
to
993c559
Compare
rebased. |
My first thought is that option That being said I'm not sure which of the two relationships is objectively better out of:
|
To really get a sense of where this is going, it helps to look at the next step that @Duologic has in mind, where you can have multiple environments within one setup. That I think will drive exactly how this is expressed. |
I'm wondering whether we should do it in one go but I'm worried this PR becomes to big. |
Please have a look at #414, which can replace this PR. |
I really like the possibilities that this brings. A few thoughts:
|
I'm a bit out here and surely didn't attend all discussions, so I might miss obvious things. I'm generally on the lines of @captncraig, where I am in favor of a slight refactor / workflow change (this PR), but #414 seems more far-reaching to me. I do not fully understand the use-case for #414, but I am sure @Duologic can explain that to me. Some random thoughts I had while reading along:
Existing behavior explicitly supports not having a
We introduce more than one way to do the same thing here, mostly for compatibility reasons. I'd really like to avoid a "basic" and "advanced" mode. Could we instead identify what makes the Jsonnet mode harder and make it much simpler so we can ship it as the one and only way? |
As mentioned in #414, it proposes an answer to the question "What if the jsonnet has multiple environments?" This can also be answered with an error message saying
The "advanced mode" does not necessarily reflect on Tanka, the way of working (basic/advanced) becomes a matter of applying conventions to your jsonnet code base instead of imposing restrictions by the runtime (Tanka). With saying basic, I mean defining the current approach as the simplest convention possible on how to use Tanka. If we want to bring out a different convention as the basic use case, then we can, for both the single environment and multiple environments pull requests.
|
re As I said before, the argument for making Any other the Jsonnet objects can exist as siblings to the Environment object, removing the assumption that all visible re The code currently looks for an Environment object, if that doesn't exist it assumes the old spec.json behavior. This is |
This moves the naming to the ParseDir() function as naming based on directory name only makes sense in the context of spec.json. Inline environments can technically be run form anywhere, only convention keeps them in this assumed directory structure.
060e0cc
to
2ee1696
Compare
pkg/tanka/parse.go
Outdated
type evaluateFunc func(path string, opts jsonnet.Opts) (string, error) | ||
|
||
// parseEnv finds the Environment object at the given path | ||
func parseEnv(path string, opts jsonnet.Opts, evalFn evaluateFunc) (interface{}, *v1alpha1.Config, error) { |
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.
Injecting big amounts of logic as a anonymous function is very hard to debug (behavior no longer obvious from function body). I will work with @Duologic to find a better way here. I wish to hold this PR until that happened, if that's okay (@malcolmholmes)
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.
I'm happy with the functionality of this PR as it is. I agree with the code being a bit more complex than it needs to be, but am also happy to see that addressed in a subsequent PR.
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.
Looks good! Some minor findings and this is good to be merged
pkg/tanka/workflow.go
Outdated
func Eval(dir string, opts Opts) (raw interface{}, err error) { | ||
r, _, err := eval(dir, opts.JsonnetOpts) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return r, nil | ||
return r, err | ||
} |
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.
What's the reason for removing this? Well behaved functions should not return data in case of errors
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.
If r
is not nil, then the evaluation succeeded, the error could still be that there is no Environment but still evaluate the jsonnet fine. Do you think I should catch that err type and only return r, nil
if it isn't about the missing environment?
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.
PTAL, I catch a few errors that have no effect on the eval itself and print them as log messages instead.
} | ||
|
||
// ErrMultipleEnvs means that the given jsonnet has multiple Environment objects | ||
type ErrMultipleEnvs struct { |
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.
Could this error message include the names of the environments? Makes it far easier to debug then
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.
config := v1alpha1.New() | ||
if err := json.Unmarshal(data, config); err != nil { | ||
return nil, errors.Wrap(err, "parsing spec.json") | ||
} | ||
|
||
// set the name field | ||
config.Metadata.Name = name |
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.
If we no longer set the name ourselves, we should enforce some name is set
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.
I've covered for this in 4ee8de7
pkg/tanka/workflow.go
Outdated
if err != nil { | ||
switch err.(type) { | ||
case ErrMultipleEnvs: | ||
log.Println(err) |
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.
I must admit I can't quite follow this code. To me this seems as encountering multiple environments is no longer a fatal thing, but only a warning, given no error is returned.
I'd expect Eval
to behave like so:
- return an
error
everytime an error occured (e.g. invalid Jsonnet, no Environment, multiple Environments, etc) - as you need to handle the "no inline but maybe spec.json" case further down the road, this specific case should also return data
This allows a naive callee to automatically abort when any error occurs. Callees aware of inline vs spec.json can react to receiving ErrNoEnv
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.
That is what I did before, simply return both data and err from upstream to downstream, no handling here.
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.
Sure, but this is different from what I just described.
Only the specific case of "no inline environment" has a reason to return data alongside an error. Afaict, all other errors that occur here are fatal and thus return nil, err
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.
Okay, so here you say return data+err? Gotcha.
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.
Yes, which you could simplify along those lines:
// Eval returns the raw evaluated Jsonnet output (without any transformations)
func Eval(dir string, opts Opts) (raw interface{}, err error) {
r, _, err := eval(dir, opts.JsonnetOpts)
if errors.Is(err, ErrNoEnv) {
return r, err
} else if err != nil {
return nil, err
}
return r, nil
}
Building further on #389, with implementing the environment inline there is no more need to encapsulate a Tanka Environment in a directory.
This proposes a solution for #390.
Might also help for #394 as the "spec" is now evaluated in Jsonnet.
This PR will allow us to use the Environment inline, like this:
It is fully backwards compatible but has a few drawbacks:
see belowtk env
is build upon the spec.json, it will be hard to support inline environments with thistk complete
might need some adjustmentsOn
tk env
:tk env add
andtk env remove
will be fairly easy to support, just need to decide on what the default layout is foradd
.tk env list
can become significantly slower as the jsonnet has to be evaluated for an Environment object to exist, we can improve on speed with go routines and only evaluate if spec.json doesn't existsI've fixed this by wrapping a snippet around the jsonnet removing thedata:
object in 2ee1696tk env set
would be really hard to port over, you'd be modifying someone's jsonnet code.