-
Notifications
You must be signed in to change notification settings - Fork 788
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
proxy: Add optional flags to OpenImage
#1844
base: main
Are you sure you want to change the base?
Conversation
0eaaa97
to
1c93bd2
Compare
See containers#1829 (comment) This is prep for adding another option like `require-signatures`. Signed-off-by: Colin Walters <[email protected]>
1c93bd2
to
44c01f7
Compare
case 1: | ||
// This is is the default | ||
case 2: | ||
// The second argument, if present should have been parsed by the caller |
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.
… but OpenImageOptional
doesn’t do that…
I think enforcing len(args)
, and turning args
into better-typed data, should be collocated close to each other.
Probably in OpenImage
(allowing len(args) in {1,2}
) and OpenImageOptional
(allowing len(args) == 1
), calling already well-typed openImageImpl(imageRef string, opts openOptions)
; or maybe have openImageImpl
fully responsible for args
, with the effect of OpenImageOptional
also allowing the new option argument. I’d prefer the former one but I don’t feel nearly as strongly about this choice as I do about having the the len
check and the code that parses argv
close.
} | ||
|
||
func (o *openOptions) parse(argsval interface{}) error { | ||
args, ok := argsval.([]interface{}) |
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’d find map[string]any
more natural here — it seems fairly likely we will want to pass non-boolean options in the future.
To go further, (preferably?), there probably is a way to use json.Unmarshal
to deal with parsing the options into a Go struct, not writing any manual parser at all. Maybe something vaguely like
type request struct {
Method string `json:"method"`
Args []interface{} `json:"args"`
Options json.RawMessage
}
type requestTableEntry struct {
handler func(options any /* sadly type-erased */ args []any) (replyBuf, error)
allocOptions func() any
}
var requestTable map[string]requestTableEntry = {
…
"Open": requestTableEntry{
handler: h.OpenImage, /* FIXME: this can't actually refer to h. here — refactor somehow */
allocOptions: func() any { return &openOptions{} },
},
}
…
func (h *proxyHandler) processRequest(readBytes []byte) (rb replyBuf, terminate bool, err error) {
var req request, options any
if err = json.Unmarshal(readBytes, &req); err != nil {
err = fmt.Errorf("invalid request: %v", err)
return
}
handler, ok := requestTable[req.Method]; if !ok { … }
if handler.allocOptions != nil {
options = handler.allocOptions()
if err = json.Unmarshal(req.Options, options); err != nil { … }
} else if req.Options != nil { /* fail */ }
return handler.handler(options, req.Args)
}
or some even more elaborate option where the handers have well-typed, not type-erased options
… but that screams YAGNI.
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.
… or, why the … not, we could architecture-astronaut this, and remove the args
/options
distinction, and all the manual casts
type parsedRequest interface{
argsDestinations() []any
/* or have ^^^ even return (mandatoryArgs []any, optionalArgs []any), and then we could farm out validating `len(args)` to the shared `processRequest` entirely */
run() (replyBuf, error)
}
type openImageRequest{
imageName string
opts openOptions /* a struct with named fields, automatically decoded by json.Unmarshal */
}
func (*r openImageRequest) argsDestinations() []any {
return []any{&r.imageName, &r.opts}
}
func (*r openImageRequest) run() (replyBuf, error)
/* use r.imageName, r.opts - no manual type checks or decoding */
}
var requestTable map[string]func ()*parsedRequest = {
"OpenImage": func()*parsedRequest{return &openImageRequest{}
}
func (h *proxyHandler) processRequest(readBytes []byte) (rb replyBuf, terminate bool, err error) {
/* unmarshal req.Args as []json.RawMessage */
newRequestState, ok := requestTable[req.Method]; if !ok { … }
rs := newRequestState()
argsDest := rs.argsDestinations()
if len(req.Args) > len(argsDest) { /* fail */ }
for i := range req.Args {
if err := json.Unmarshal(Args[i], argsDest[i]); err != nil { … }
}
return rs.run()
}
Honestly I have no idea if this ends up actually shorter. There are not that many operations that avoiding manual parsing of args
is clearly a win, and the parsedRequest
implementations are just another kind of boilerplate.
The above still feels clumsy, like I’m missing some obvious way to have the standard library do all the work.
(OTOH If we keep going like this, I’m sure we will eventually end up using something like Swagger…)
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.
(s/any/interface{}/g
, I think we don’t yet require Go 1.18, but that might change soon.)
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 above still feels clumsy, like I’m missing some obvious way to have the standard library do all the work.
One more thought: Get rid of the untyped Args
entirely, use named parameters only (doesn’t help for any existing API with the Args []any
)
type sharedRequestHeader struct {
Method string `json:"method"`
}
type parsedRequest interface{
run() (replyBuf, error)
}
type openImageRequest{
sharedRequestHeader
ImageName string `json:"imageName"`
Opts openOptions `json:"opts"`
}
func (*r openImageRequest) run() (replyBuf, error)
/* use r.imageName, r.opts - no manual type checks or decoding */
}
var requestTable map[string]func ()*parsedRequest = {
"OpenImage2": func()*parsedRequest{return &openImageRequest{}}
}
func (h *proxyHandler) processRequest(readBytes []byte) (rb replyBuf, terminate bool, err error) {
var header sharedRequeestHeader
if err := json.Unmarshal(readBytes, &header); err != nil { … }
newRequestState, ok := requestTable[header.Method]; if !ok { … }
rs := newRequestState()
if err := json.Unmarshal(readBytes, rs); err != nil { … } // re-parse now that we know the request type
return rs.run()
}
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.
Doh, if we unmarshaled the whole request as a method-specific struct
, we wouldn’t need the openOptions
sub-struct at all (though it wouldn’t hurt).
Thanks for the review! I mentioned this in passing before but I have been vaguely mulling a switch to e.g. Cap'n Proto, which would obviate all of this. Any opinions on that? If we did it in the obvious way it would involve sending the blob data over the primary channel, but maybe that's OK. (As of lately cpnp does support fd passing, but support is not yet widespread in the bindings; at least not Rust which is mainly what I care about) |
You did mention that — it the website looks nice. I didn’t take a deep look and I don’t really have a strong opinion on RPC mechanisms, I know far too little about the various trade-offs. https://capnproto.org/otherlang.html seems sufficient for interoperability. I guess the main concern would be total binary size — that should not be an issue in principle, but I’m just dealing with a Swagger generator that somehow adds a BSON encoder and a telemetry framework as non-avoidable dependencies. I guess to be clearer: from the stream-of-consciousness thread #1844 (comment) , the only thing I’m really concerned is that the options argument should be a JSON object, not an array. Parsing that JSON object with a hand-crafted The other things about that possible refactoring are not blocking this work. It’s just me slowly realizing that the |
A friendly reminder that this PR had no activity for 30 days. |
A friendly reminder that this PR had no activity for 30 days. |
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.
Hi
See #1829 (comment)
This is prep for adding another option like
require-signatures
.Signed-off-by: Colin Walters [email protected]