-
Notifications
You must be signed in to change notification settings - Fork 38
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 producer API to write specs #233
base: main
Are you sure you want to change the base?
Conversation
39d1b55
to
1282f64
Compare
pkg/cdi/producer/api.go
Outdated
) | ||
|
||
// a specValidator is used to validate a CDI spec. | ||
type specValidator 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.
nit: Shouldn't we export this interface, too (even if it is so trivial) ?
The package-provided Default
and Disabled
implementations are exported (and are supposed to be referenced directly as such, and not for instance indirectly by a name, etc.). I am guessing that we want to allow folks to plug in their own validation logic. Since (assuming those folks use the stock c-d-i package also for eventual interpretation/injection) custom validators should always be stricter than the default one (to ensure injection by the same package wouldn't fail). That suggests that most custom validators will be a composition of the default validator + some extra custom restrictions.
Now, it is already possible to do composition using the default validator, but if my aforementioned assumptions are true, it feels strange to not expose the interface itself.
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.
My thinking was that one could plug in the schema validators here as well. I had some local changes that would expose those as interfaces instead of as raw function pointers.
I'm happy to expose the interface as public though to make the intent clearer and allow users to compose these as required.
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.
Having the ability to plug in additional schema-based validation here feels like a good idea.
3205f41
to
7ef814c
Compare
@klihub I simplified this PR a bit to start with. If you're ok with the general direction, we can get this in and then move some of the other functionalty (annotations, device name generation) to this package as well. I would also like to add functionality to allow the minimum verison to be determined at save, but would rather do that as a follow up. |
pkg/cdi/producer/api.go
Outdated
// A SpecValidator is used to validate a CDI spec. | ||
type SpecValidator interface { | ||
Validate(*cdi.Spec) 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.
Would it make sense to move this to the specs-go
package? Validation is generally usable on both producer and consumer side, I guess.
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 want to add unnecessary things to the specs-go
package. I think we made the call that code to determine the minimum version was close enough to the spec to implement there, but I think an operation on the spec such as validation is not.
Note that due to the nature of go interfaces, this type need not be exported at all -- although it may be useful if it is.
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.
Sorry for not being clear with this. I didn't mean moving only SpecValidator interface. I was talking about moving the whole validator package there. The idea was that it doesn't logically belong to producer. Having it here looks a bit confusing to me.
There is at least one reason not to move it to the spec module though. If it brings a lot of additional external dependencies to the spec package, we should probably avoid doing 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.
I would rather suggest that we move the version specifics out of specs-go
implement them as part of the "producer" package. Ideally we would implement this as a submodule as well so that we can properly separate the dependencies.
In the end we will have specs-go
defining the actual data structures and helpers around this -- including validation. Implemented in other packages / submodules.
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.
Sorry for not being clear with this. I didn't mean moving only SpecValidator interface. I was talking about moving the whole validator package there. The idea was that it doesn't logically belong to producer. Having it here looks a bit confusing to me.
There is at least one reason not to move it to the spec module though. If it brings a lot of additional external dependencies to the spec package, we should probably avoid doing this.
Yes, I had the same gut feeling. As long as the producer and the consumer were under a single package, having the validator as a subpackage thereof felt like a logical choice. Now if we split them, it does not feel so natural to have it under either... so maybe a pkg/validator
. This can be adjusted with subsequent PRs, too.
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 actually wondering whether we shouldn't make this split at a module level so as to better manage dependencies. Meaning that we'd have:
tags.cncf.io/container-device-interface/api/v1/consumer
tags.cncf.io/container-device-interface/api/v1/producer
as top-level go submodules.
For the producer specifically, we would start out with:
tags.cncf.io/container-device-interface/api/v1/producer/cdi
with the types in this package.
We could keep the existing pkg/cdi
package to provide backward compatibility.
A client that produces specs would then be something like:
package main
import (
"tags.cncf.io/container-device-interface/api/v1/producer/cdi"
)
func main() {
raw := generateRawCDISpec()
saver, _ := cdi.New(raw, cdi.WithPermissions(0644))
err := saver.Save("/var/run/cdi/example.json)
}
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 client that produces specs would then be something like:
package main import ( "tags.cncf.io/container-device-interface/api/v1/producer/cdi" ) func main() { raw := generateRawCDISpec() saver, _ := cdi.New(raw, cdi.WithPermissions(0644)) err := saver.Save("/var/run/cdi/example.json) }
As abstractions go, it feels a bit artificial to have a 1-to-1 association between a producer and a Spec, IOW to essentially make SpecProducer
a simple Spec wrapper. I would expect a producer to produce one or more Spec files, typically with the same file format and permissions (at least by default). So maybe I'd allow passing those as options to the constructor, and rather pass the raw spec to be written to a Save or some similar function of the producer, but not to its constructor itself.
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's a fair point. One question in this regard, let's say that the producer is configured to set the minimum version of the spec on save is it OK to modify the spec in-place?
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 the producer specifically, we would start out with: tags.cncf.io/container-device-interface/api/v1/producer/cdi
It'd look a bit inconsistent that producer and consumer APIs have a bit different paths. I'd propose to use
tags.cncf.io/container-device-interface/api/v1/producer and tags.cncf.io/container-device-interface/api/v1/consumer as you've proposed above.
pkg/cdi/producer/producer.go
Outdated
pWithFormat, err := cloneWithOptions(p, WithSpecFormat(format)) | ||
if err != nil { | ||
return "", err | ||
} | ||
return pWithFormat.Save(filename) |
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.
nit: again, this it not a biggie, but it feels a bit weird, having to 'clone' the producer just to change the format... and clone is only used for 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.
An initial implementation that I had was to have a separate type to do the writing / saving and to construct this when Save
is called. Let me look at it a bit again and see if I can come up with a better abstraction.
pkg/cdi/producer/options.go
Outdated
} | ||
|
||
// WithSpecFormat sets the output format of a CDI specification. | ||
func WithSpecFormat(format SpecFormat) Option { |
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.
nit: Perhaps call it WithDefaultSpecFormat()
... now it is possible to configure a writer supposedly for a format, which is then overridden by the filename extension if it does not match... so semantically this is a default format.
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.
Note that the type also exposes a WriteTo(io.Writer)
function which is where this spec format is used. As you note, the Save(string)
function does override the format to be consistent with the filename.
Happy to change the method name though.
a34dc06
to
a66a415
Compare
Signed-off-by: Evan Lezar <[email protected]>
This change adds a SpecProducer that can be used by clients that are only concerned with outputing specs. A spec producer is configured on construction to allow for the default output format, file permissions, and spec validation to be specified. Signed-off-by: Evan Lezar <[email protected]>
This change proposes a "producer" API for generating and writing specs.
The more general
cdi
package imports this package when writing specs.