-
Notifications
You must be signed in to change notification settings - Fork 365
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
Add a template server #3600
Add a template server #3600
Conversation
See template-server/template-server.md for design, and ttv-wizard.mvp for (first) application.
d7bd5eb
to
7aa8754
Compare
Guide to reviewingApologies for a large PR, but it actually may be easier to review than it seems at first.
|
type: string | ||
description: URL of the template; must be relative (to a URL configured on the server). | ||
- in: query | ||
name: params |
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.
Does that mean that if I want to pass additional params I would need to add something like:
<URL+PATH>?params.a=1¶ms.b=2...
?
Is it a common practice to do this?
Wouldn't be more cleaner to pass <URL+PATH>?a=1&b=2...
?
pkg/templater/expander.go
Outdated
type Expander interface { | ||
// Prepare checks whether the template expansion will succeed. Call | ||
// it before Expand to ensure nothing is written when expansion fails. | ||
Prepare(params *Params) 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.
Why do you need this TPE (two-phase-expand)?
Can't you use Expand
to try and Execute
to a temporary bytes.Buffer{}
, return error if it doesn't succeed, and write to the given Writer if it does succeed?
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 could, but I am not sure that it would be better than this. One disadvantage: I worry that the buffer might be large. As an example, suppose someone abused template to serve a large object (maybe even static, say an image). Then we would hold it in process memory, which is not fun for the process.
Are there advantages other than simpler implementation?
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 do think that simpler implementation (thus better maintainability) is important enough, but the concerns you're raising are more important.
Regardless you can still collapse it to a single step process and do the validation step as a first step of the expansion... I think that it's not the role of the client to check if the template is expandable or not.
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.
Sounds good! I shall rework the logic from service.Expand
into a single call 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.
Thanks!
I think we should really decide on 2-phase vs. 1-phase processing. What's better about 1-phase, other than simpler implementation?
pkg/templater/expander.go
Outdated
type Expander interface { | ||
// Prepare checks whether the template expansion will succeed. Call | ||
// it before Expand to ensure nothing is written when expansion fails. | ||
Prepare(params *Params) 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.
We could, but I am not sure that it would be better than this. One disadvantage: I worry that the buffer might be large. As an example, suppose someone abused template to serve a large object (maybe even static, say an image). Then we would hold it in process memory, which is not fun for the process.
Are there advantages other than simpler implementation?
|
||
// WrapFuncMapWithData wraps a funcMap to a FuncMap that passes data as a | ||
// first element when calling each element. | ||
func WrapFuncMapWithData(funcMap template.FuncMap, data interface{}) template.FuncMap { |
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.
Trying to figure out why is it necessary.
The output of this function is an array of functions (FuncMap) that contains functions that hold some data (that would be the controlled data in the implementation), and that appends that "hold-data" to the given argument-data, then calls the underlying function on the total-data.
So a number of questions:
- Is my understanding correct?
- Why not initialize the template structure with the original FuncMap, and pass the total-data (controlled + uncontrolled) to Execute()?
- If it's still necessary to wrap the functions, can we wrap those functions and values in functions instead of reflections to make it more understandable (and probably maintainable)?
By "wrapping with functions" I mean something like:
ret[k] = func(args ...interface{}) (interface{}, error) {
argVals := make([]interface{}, 0, len(args)+1)
argVals = append(argVals, data)
argVals = append(argVals, args...)
return v(argVals)
}
(holding the controlled data and the underlying function)
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.
By points...
(1) Yes, this is what this function does.
(2) Execute() is somewhat broken: I could find no mechanism to pass side-data into functions. The 2nd arg data
does not work for this, because any exported members of it are visible not only to functions but also to the template being expanded. And I am afraid of that access in templates, I have less control over them and they are a DSL that is difficult to analyze. In particular note the template function call
, and consider how much damage a user allowed to create new templates will be able to do with the auth service, if that is supplied on data
.
(3) I am not sure why wrapping functions manually is less error-prone than performing it automatically. I prefer to pay the added price of having to understand reflection now but once, to having to check each definition of a function wrapper. Also I don't think that your example will compile without redefining v
to take all arguments as interface{}
. The way to do it would probably be to specify explicitly the types of all arguments to v
, and just copy the argument list into the call to v
.
schema: | ||
format: binary | ||
401: | ||
$ref: "#/components/responses/Unauthorized" |
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 about 403 Forbidden?
The user might be authenticated but not authorized to fetch the template or use the requested function
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.
For some reason we never specify 403 in this file. @nopcoder do you know why? In any case, I'm not blocking on this.
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.
Really awesome!!!
Not requesting changes but rather some explanations (no such radio button yet)...
Just have a single Expand call that expands twice, once to `io.Discard` and once to the real writer.
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!
schema: | ||
format: binary | ||
401: | ||
$ref: "#/components/responses/Unauthorized" |
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.
For some reason we never specify 403 in this file. @nopcoder do you know why? In any case, I'm not blocking on this.
pkg/templater/expander.go
Outdated
type Expander interface { | ||
// Prepare checks whether the template expansion will succeed. Call | ||
// it before Expand to ensure nothing is written when expansion fails. | ||
Prepare(params *Params) 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.
Sounds good! I shall rework the logic from service.Expand
into a single call here.
|
||
// WrapFuncMapWithData wraps a funcMap to a FuncMap that passes data as a | ||
// first element when calling each element. | ||
func WrapFuncMapWithData(funcMap template.FuncMap, data interface{}) template.FuncMap { |
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.
By points...
(1) Yes, this is what this function does.
(2) Execute() is somewhat broken: I could find no mechanism to pass side-data into functions. The 2nd arg data
does not work for this, because any exported members of it are visible not only to functions but also to the template being expanded. And I am afraid of that access in templates, I have less control over them and they are a DSL that is difficult to analyze. In particular note the template function call
, and consider how much damage a user allowed to create new templates will be able to do with the auth service, if that is supplied on data
.
(3) I am not sure why wrapping functions manually is less error-prone than performing it automatically. I prefer to pay the added price of having to understand reflection now but once, to having to check each definition of a function wrapper. Also I don't think that your example will compile without redefining v
to take all arguments as interface{}
. The way to do it would probably be to specify explicitly the types of all arguments to v
, and just copy the argument list into the call to v
.
You're still requesting changes, so asking for a re-review :-) |
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!
I think that the template itself should be changed...
This should makes it immediately and directly usable. Co-authored-by: Jonathan Rosenberg <96974219+Jonathan-Rosenberg@users.noreply.github.com>
* Move `template_location` to the URL path. This makes the OpenAPI interface cleaner: the URL query now contains just the parameters of the template. * [CR] Make the `spark.conf.tt` template the finished required form including `lakefs_url` in it. * Fix **bug** in OpenAPI golang code generation (on both client and server sides) with freeform query params in the specification: it does not understand `expand: true`. This is documented as how to do it, generates correct URLs in online OpenAPI editors, but code generated is incorrect (seems to think the dummy params object is actually where it will place the parameters, when in fact it does not.
Did that, which uncovered a BUG in OpenAPI code generation (and that the previous code never really worked). Updated version includes the requested template and works. PTAL... |
Thanks! |
See design at template-server.md.
Has only the minimal functions required to build the MVP.
Implements #3540 .