-
Notifications
You must be signed in to change notification settings - Fork 116
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
kubectl -f
-style support for deploy task
#514
Conversation
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.
A few points for discussion
57cabbe
to
79d77dc
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.
Some tidying up and implementation of Blake's suggestion + a few more comments for discussion
lib/kubernetes-deploy/render_task.rb
Outdated
@@ -29,7 +29,7 @@ def run!(stream, only_filenames = []) | |||
@logger.phase_heading("Initializing render task") | |||
|
|||
filenames = if only_filenames.empty? | |||
TemplateDiscovery.new(@template_dir).templates | |||
TemplateDiscovery.new([@template_dir]).templates[@template_dir] |
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.
This is the result of the change in contract of TemplateDiscovery#new
requiring an array of template dirs.
- Do we want the render task to accept multiple template dirs. Note that the render task can also accept an arbitrary list of filenames as a filter. For multiple template dirs, we'll just apply the filter to each directory.
- If we do want the render task to accept multiple template directories as well, do we want to tackle that in this PR or in a follow-up?
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 don't think that accepting multiple directories makes sense the way the renderer's CLI is currently implemented. For example, imagine you have a setup like this:
dirA/
one.yml
two.yml
dirB/
one.yml
two.yml
And what you want to render is dirA/one.yml and dirB/two.yml. You... couldn't?
According to our design for the Krane CLI, we will want both commands to use a -f
argument that behaves similarly to kubectl's. That strikes me as incompatible with a directory/filter system: in that design, we'd get a mixed list of files and directories, and have to group individual files given to us by (inferred) deploy directory. Given the importance of the directory to us, maybe the -f
plan doesn't make sense after all, because it would lead to unintuitive behaviour. WDYT?
In any case, I'm thinking it would be a breaking API change for the render CLI (unlike the deploy CLI), and if that's the case, we should save it for the transition to Krane.
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 think you're absolutely right. The current semantics of kubernetes-render
are incompatible with a kubectl -f
-like model. For instance, there would be no way to know which bare file names provided to kubernetes-render
point to which provided template dirs :/
To me, this means we need to make a fundamental change to the CLI contract of kubernetes-render
. In particular, dropping support for providing bare filenames as arguments and instead force the (conventional) semantics of the -f
flag. From your above example, this would be:
krane render -f dirA/one.yml,dirB/two.yml
(not that bad).
In light of this, I'd like to keep kubernetes-render
as-is (only supporting a single template dir), and make a follow-up PR to make krane render
behave with -f
semantics.
# Exposed for direct use only when deploy_fixtures cannot be used because the template cannot be loaded pre-deploy, | ||
# for example because it contains an intentional syntax error | ||
def deploy_dir(dir, **args) | ||
def deploy_dir(*dirs, **args) |
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.
Multiple template directories are exceptional, so it seemed more straightforward to change the signature to accept an arbitrary number of dirs
as opposed to enforcing an actual array. I can do the latter if that's what we ultimately want.
end | ||
|
||
def test_deploy_successful_with_multiple_template_dirs_multiple_partials | ||
result = deploy_dir(fixture_path("test-partials"), fixture_path("test-partials2"), |
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.
The goal of this test is to ensure that partials with identical names are actually rendered according to the relative path of the given template directory. This was done by using an identically-named partial that renders different parts of the spec; if the 'wrong' partial is used, the deploy would fail. This convinces me enough that the renderer for a given template directory is behaving as expected (e.g. properly scoped to the right dir)
19f2e3d
to
a60dbcf
Compare
My original concern with this PR was how will it interact with our goal of supporting a |
At the very least, I don't think this PR makes things any more difficult in achieving that end. The sole complicating factor between
|
001722e
to
78b72bd
Compare
lib/kubernetes-deploy/render_task.rb
Outdated
@@ -29,7 +29,7 @@ def run!(stream, only_filenames = []) | |||
@logger.phase_heading("Initializing render task") | |||
|
|||
filenames = if only_filenames.empty? | |||
TemplateDiscovery.new(@template_dir).templates | |||
TemplateDiscovery.new([@template_dir]).templates[@template_dir] |
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 don't think that accepting multiple directories makes sense the way the renderer's CLI is currently implemented. For example, imagine you have a setup like this:
dirA/
one.yml
two.yml
dirB/
one.yml
two.yml
And what you want to render is dirA/one.yml and dirB/two.yml. You... couldn't?
According to our design for the Krane CLI, we will want both commands to use a -f
argument that behaves similarly to kubectl's. That strikes me as incompatible with a directory/filter system: in that design, we'd get a mixed list of files and directories, and have to group individual files given to us by (inferred) deploy directory. Given the importance of the directory to us, maybe the -f
plan doesn't make sense after all, because it would lead to unintuitive behaviour. WDYT?
In any case, I'm thinking it would be a breaking API change for the render CLI (unlike the deploy CLI), and if that's the case, we should save it for the transition to Krane.
name: web-from-partial | ||
spec: | ||
replicas: 1 | ||
template: |
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.
Nitpick: only this part is a podspec, despite the name of the file.
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.
The goal of the test is to ensure that it's not loading the identically named partial in the other directory, so I kept the partial name the same but ensured the render would fail to validate if it grabbed the wrong partial
lib/kubernetes-deploy/deploy_task.rb
Outdated
@@ -102,22 +102,24 @@ def server_version | |||
kubectl.server_version | |||
end | |||
|
|||
def initialize(namespace:, context:, current_sha:, template_dir:, logger:, kubectl_instance: nil, bindings: {}, | |||
def initialize(namespace:, context:, current_sha:, template_dirs:, logger:, kubectl_instance: nil, bindings: {}, |
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 was trying to think of ways to avoid this breaking API change, but the options that occur to me aren't pretty:
- Keep
template_dir:,
but let it accept an array and do@template_dirs = Array(template_dir)
in the initializer - have both
template_dir:,
andtemplate_dirs:
, either the initializer merges them or it raises an error if they're both specified
It kinda irks me to make a breaking API change like this soon before we make another one (with Krane, which is a single clean cutover in a way). I'd be kinda ok with something like the second option above as a result. If we keep it breaking though, let's make it very prominent in the changelog.
Can you comment on why the cleaner approach didn't work out? |
The renderer takes a few arguments that are easily grabbed in the task initializer. In the end, it seemed easier to wrap those in the closure provided by the default hash block than try to pass them into |
I can't comment in the thread but, I like the idea. As an fyi we have a class called |
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 like this a lot better, thanks for putting in the work to refactor it!
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.
This is ready for a (hopefully final) review
lib/kubernetes-deploy/deploy_task.rb
Outdated
@namespace = namespace | ||
@namespace_tags = [] | ||
@context = context | ||
@current_sha = current_sha | ||
@template_dir = File.expand_path(template_dir) | ||
@template_dirs = (template_dirs.map { |dir| File.expand_path(dir) } << template_dir).compact |
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.
We don't need to worry about the case of template_dir
and template_dirs
both existing when invoking via CLI, but I've done it this way to make it accommodate the case of someone using the API itself.
lib/kubernetes-deploy/deploy_task.rb
Outdated
errors << "Template directory `#{template_dir}` doesn't exist" | ||
elsif Dir.entries(template_dir).none? { |file| file =~ /(\.ya?ml(\.erb)?)$|(secrets\.ejson)$/ } | ||
errors << "`#{template_dir}` doesn't contain valid templates (secrets.ejson or postfix .yml, .yml.erb)" | ||
end |
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.
This whole block is another thing that should be extracted out of DeployTask
if possible
hash[dir_name] << File.basename(filename) unless hash[dir_name].include?(filename) | ||
end | ||
|
||
template_paths |
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.
Now any task should be able to call TemplateDiscovery#templates
and receive back a mapping of dirname: [files]
. The input of templates
is an array whose elements are either file paths or directories (abs and/or relative). The nice thing here is that there's zero other dependencies for generating the dir->files mappings
kubectl -f
-style support for deploy task
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.
Implemented the TemplateSets
abstraction I talked about with @dturn . It actually ends up cleaning things up quite a bit since we now have a single API interface to deal with arbitrary numbers of template paths.
I've added unit tests and fleshed out the validation for TemplateSets
.
Also realized a silly error I made and corrected it: -f
flag can now be invoked multiple times
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 really like this refactor, my only real question is the params to new_from_dirs_and_files
.
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.
Let's be sure to 🎩 this before merging
Different approach with multiple renderers rubocop change ejson_secret_provisioner to handle multiple template dirs rejig tests due to method signature/name changes more test updates actually a tab: use \t instead of actual tab (for linter) fixup refactor to avoid passing template_dirs all around hacky fix for render task, add test for partial naming collision test result, not forced failure deploy_dir -> deploy_dirs Add assert_logs_match tests to multi-template-dir tests ensure ejson secrets from multiple template directories are created rubocop fixes avoid nilref error possibility in OptionsHelper#default_template_dir force unique template dirs support template_dir (deprecated) and template_dirs TemplateDiscovery -> ResourceDiscovery rubocop pass namespace properly pass filename from raise InvalidTemplateException reraise Psych::SyntaxError as InvalidTemplateError error handling dance yet more error handling ResourceDiscovery -> LocalResourceDiscovery whitespace tidy up DeployTask#discover_resources reject(&nil?) -> compact optionshelper test + refactor rubocop tidy up exe/kubernetes-deploy for easier deprecation + checking the return of template discovery + proper -f handling (files + dirs) tests for filename handling as arg lots of renaming deal with ejsonsecretprovisioner case. more naming fixes TemplateDiscovery cleanup testing partials, secrets, with filenames lint test make render cli work. rename some flag fields CHANGELOG handling ejsonsecretprovisioning with new template args statsh Move back to multiple ejsonprovisioners. EjsonProvisioner must be supplied a secret, it no longer fetches one itself fix File.join bug VALID_EXTENSIONS shouldn't include secrets.ejson remove unused memo for TemplateDicovery::resource_templates (method is now a class method, not instance) use fixture_path instead of relative path for test. Remove redundant condition from default_template_dir in options_helper rubocop README some pr review fix render task no-template-dir-provided case. Pass ejson file, not simply dirname stash changes stash working template_set implementation (need to rejig tests) rubocop + basename(filename) fix for record_invalid_template" dealing with secrets, however sloppily validations beginnings stash fix rendering error not propogating message properly more dir validation fix test remove period code cleanup remove unused code ensure we use config/deploy/$ENVIRONMENT if no template_dir(s) given
…ling name as a file, not a dir. Need to fail fast on OptionsHelper instead
6c9cbd4
to
790f010
Compare
Katrina is off on vacation and this has gone through peer review in her absence
What are you trying to accomplish with this PR?
-f
, with similar semantics tokubectl -f
-f
accepts a comma-separated list of filenames and/or directoriesdeploy_task
, but the-f
parsing is done in such a way as to be reusable (viaTemplateDiscovery#templates
)--template-dir
is still supported (both at the CLI and API level), but should be considered deprecated once this PR mergesUpdate (Aug 15, 2019)
The scope of this PR expanded from just supporting multiple template directory to full fledged support for passing in both files and directories. This approach necessitated a few changes to some internal abstractions, namely
TemplateDiscovery
and the newLocalResourceDiscovery
.TemplateDiscover::templates(paths)
returns a mapping of the formdir -> [filenames]
. As an example, let's say thepaths
argument is[my/dir, other/foo.yml]
and the contents ofmy/dir
andother
are[bar.yml, baz.yml
] and[foo.yml, more.yml]
. The return value oftemplates
will thus be:TemplateDiscovery
performs proper aggregation of resources and eliminates the possibility of duplicate values existing. Furthermore, proper partial scoping is preserved since we maintain a mapping of directories for all kinds of resources (both dirs and files).How is this accomplished?
Initially, I tried passing multiple template directories around
deploy_task
, but this became cumbersome: I had to create a mapping of template directories to their respective partials dirs, wrap them together, and pass them to and fromTemplateContext
objects and back to the (somewhat-global) renderer.A cleaner approach was to simply instantiate a separate renderer for each template directory passed in, then, when appropriate, concatenate the result of each template directory rendering operation and proceed through the deploy as normal. This way, the changes to the codebase are much smaller and the logic for each individual element stays simpler.
Edit: What I ended up doing in the end is a bit of a mix of the first 2 ideas. In the deploy task constructor, we declare a mapping of renderers to template directories. The renderers are then lazily instantiated directly at the point of rendering (in
#split_templates
), leveraging the closure provided by the default block of the@renderers
Hash (thank you Blake for the great suggestion).Please see the unresolved comments from my review for more fine-grained issues regarding this PR.
What could go wrong?
How defensive should we be? I think that giving people the ability to specify multiple template directories places the responsibility of ensuring they end up with a sane and collision-free experience squarely on them.
TODO