SIP-024: spin deps cli dx#3453
Conversation
2a722ee to
a99e8ea
Compare
Signed-off-by: Brian Hardock <brian.hardock@fermyon.com>
a99e8ea to
7708392
Compare
itowlson
left a comment
There was a problem hiding this comment.
LGTM. I like the capabilities stuff; in fact I almost wonder if I should be able to run the capabilities stuff outside of doing an add.
|
|
||
| Selecting **"All from aws:client@1.0.0"** records `aws:client@1.0.0` as the dependency name (a package-level selector). Selecting a specific interface records that interface (e.g. `aws:client/s3@1.0.0`). | ||
|
|
||
| #### Explicit `--export` flag |
There was a problem hiding this comment.
This is not surfaced interactively, right? (I would like it not to be - I just want to check we are on the same page.)
oh wait, this is the interface on the left hand side, not the export = option? That coincidence of terminology is going to be a trap
There was a problem hiding this comment.
They're equivalent when selecting interfaces. I couldnt think of a better name here that encapsulated packages and interfaces. How do you feel about --use?
There was a problem hiding this comment.
Wait once again it seems I am utterly confused about a feature we have been shipping for a gazillion years - I thought export was about remapping interfaces... sigh
I was initially going to suggest --interface, but I now realise that if you want all a package's APIs, you have to tell it the package name if you don't want it to interact. --use seems a bit open-ended but could be an option. We could try --import perhaps? It's not great though. Well maybe it's okay, I originally thought "ugh, importing exports" but well, huh, that is what importers do, so maybe it makes sense?
Or send up the BIKESHED BAT-SIGNAL and let Lann come up with something.
There was a problem hiding this comment.
I think import works. We could also keep export for the remapping scenario you mention which is the point of it. All I meant was that if the LHS is an interface, it implies that an export with that name exists with that name so its technically equivalent.
There was a problem hiding this comment.
I.e. "a:b/c" = { export = none, ... } is equivalent to "a:b/c" = { export = "a:b/c", ... }
There was a problem hiding this comment.
Aha, I think I see what you mean now. Thanks!
There was a problem hiding this comment.
We could allow add a mutually exclusive --package/-p, --interface/-i.
There was a problem hiding this comment.
Actually never mind, I forgot about plain names.
|
|
||
| ? Select capabilities to inherit from the parent component | ||
| > All | ||
| allowed_outbound_hosts |
There was a problem hiding this comment.
Presumably these will be check boxes rather than a single-select?
|
|
||
| - `--inherit true` or `--inherit all` → inherits all capabilities | ||
| - `--inherit false` or `--inherit none` → inherits nothing | ||
| - `--inherit allowed_outbound_hosts,ai_models` → inherits only those capabilities |
There was a problem hiding this comment.
Or --inherit allowed_outbound_hosts --inherit ai_models, right? (that is, as well as not instead of)
|
|
||
| #### Multiple packages — select a package first | ||
|
|
||
| If the component exports interfaces from multiple packages, the user first selects a package: |
There was a problem hiding this comment.
I worry with this that we may be asking a question the user doesn't know how to answer. I want the authentication interface, is that in client or util? If I guess wrong, is there a way back?
Possible alternative approaches:
- Show them a list of everything they could import - all the interfaces plus an "everything" for each package.
- When they come to interface selection, offer a "The interface I'm looking for isn't here. Take me back to package selection" option
Or maybe this isn't a concern and we have high confidence that users will know what they are looking for and just want to save on ye olde typing, I am not sure.
There was a problem hiding this comment.
Good call out. Ill have a tinker and see if i could smooth this out. I think listing everything with all options for each package is the way to go.
There was a problem hiding this comment.
I was thinking something similar, but you might run into an issue where there's just a TON of exports in a given dependency, and navigating them manually would get annoying.
Counterpoint is of course that that won't happen super-often and you only need to do this once/infrequently, so I'd also be in favor of having just 1 flat list of everything with the "All from DEP" in the list as just another option.
There was a problem hiding this comment.
That's a good point @rmarx. I don't anticipate dependencies that will have an overwhelmingly large # of exports so I agree with your counterpoint.
| After a package is selected (or if there is only one), the user chooses between all exports from that package or a single specific interface: | ||
|
|
||
| ``` | ||
| ? Which export should be used? |
There was a problem hiding this comment.
Nit: I would avoid saying 'export' here (because I want to import something, where is the choice for that). Consider e.g. "Which interface do you want to import?" or some better wording that avoids the whole import/export debacle altogether (a la "which interface do you want to use" although that's hardly going to set the poetry world alight either)
There was a problem hiding this comment.
I had a similar feeling when seeing the CLI argument is called --export instead of --import... it depends on how you want to look at it of course.
However, since I'm adding a dependency to a specific component, I probably want that component to import/use stuff from the dependency (I don't necessarily want to think about the stuff the dependency exports).
Kind of like a github PR should really be a Merge Request instead? :D (imo)
Either way, if we keep --export (instead of --import or --interfaces) I agree it would be more sensible to have some other language here like suggested by @itowlson
EDIT: just now reading the whole discussion on this below :)
| | Form | Example | Description | | ||
| |------|---------|-------------| | ||
| | Local path | `./my-component.wasm` | A path to a Wasm component on disk | | ||
| | HTTP URL | `https://example.com/component.wasm` | A remote Wasm component (requires `--digest`) | | ||
| | Registry reference | `aws:client@1.0.0` | A package from a component registry | |
There was a problem hiding this comment.
Should we also support adding by component ID? I am imagining a case where we have a component that we already build as part of the manifest that we would like to add as dependency to another.
There was a problem hiding this comment.
Strong agree on this one. I don't want to have to know/copy-paste the build output path of my Rust component to add it as a dependency of my TS component in the same spin project. I want that to be auto-figured out from the component ID
|
|
||
| The `--export` flag accepts the same forms: | ||
|
|
||
| - **Specific interface:** `--export aws:client/s3@1.0.0` |
There was a problem hiding this comment.
EDIT: just read the bottom of the doc saying that multiple exports are not yet in-scope. Decided to leave the comment though, as I do need we can/should make it a bit clearer in this doc/eventual documentation that it's intended you can only add 1 interface at a time.
Just want to confirm if/how multiple exports work. I assume that
--export aws:client/s3@1.0.0,aws:client/sqs@1.0.0
and
--export aws:client/s3@1.0.0 --export aws:client/sqs@1.0.0
would both work? Or is it always just 1 export at a time?
If there are multiple possible at once, then some text above needs to be adjusted (e.g., "Which exportS should be used").
If however you can only do 1 interface at a time, that can probably also be made a bit clearer in the text/description that people would run multiple spin deps add commands for multiple exports, even if from the same package.
| ai_models | ||
| ``` | ||
|
|
||
| Selecting **"All"** sets `inherit_configuration = true` in the manifest. Selecting individual capabilities records them as a list (e.g. `inherit_configuration = ["allowed_outbound_hosts"]`). Selecting nothing results in no inheritance. |
There was a problem hiding this comment.
So is that inherit_configuration = [] or simply no inherit_configuration?
There was a problem hiding this comment.
@rmarx They are equivalent. (Well, there is an edge case in which they are not equivalent. But we should catch that first and not ask the question if we are in that edge case! @fibonacci1729 I'm thinking of where the component already has a dependencies_inherit_configuration field.)
There was a problem hiding this comment.
Could you say more about the edge case? I'm not sure i'm following.
There was a problem hiding this comment.
The edge case is where dependencies_inherit_configuration is set. In that case, no inherit_configuration is acceptable, and inherit_configuration = [] is an error (because it implies "inherit nothing" which contradicts dependencies_inherit_configuration). So if DIC is already set on the component, spin dep add shouldn't ask about capabilities.
| ``` | ||
| Added aws:client@1.0.0 to component 'api-server' | ||
|
|
||
| NOTE: This dependency requires the following capabilities: allowed_outbound_hosts, ai_models |
There was a problem hiding this comment.
Is this printed only for capabilities that were not inherited?
Would be confusing if you explicitly chose --inherit all or --inherit ai_models and then still saw this kind of message at the end?
The example below does indicate this warning would be present if inheriting, so I think that's not ideal.
There was a problem hiding this comment.
Reading it a bit more, it seems it's saying you might need to actually fill in ai_models on the PARENT component, even if you added it as --inherit here.
Conceptually that makes sense, but it's confusing imo.
Is there a way to check if the parent component has the capabilities already filled out and only print this type of warning if it doesn't?
There was a problem hiding this comment.
@rmarx Hmm, good question. If the dep used the llm API, you would need the parent component to have ai_models so that the dep had something to inherit, and you would need the dep to inherit that setting.
We need to print a warning if the dep uses llm and parent has ai_models but the user declined to inherit it. We might ideally print a slightly different warning if the dep uses llm and inherits ai_models but the parent doesn't set ai_models. And maybe another one if the dep doesn't inherit and the parent doesn't set it...!
There was a problem hiding this comment.
Yeah, strongly in favor of more fine-grained warnings here :)
Another thing I was thinking about just now re-reading this: what happens if the user removes something related to this from the .toml down the line (e.g., removes ai_models from the parent while keeping it as a requirement/inherits on the dependency). Will spin build do a similar check as spin deps add to detect that and issue a warning (as it probably should)?
There was a problem hiding this comment.
That's a good question. Naively, the behaviour would be that you inherit the parent's ai_models permissions, which are "nothing." So if the dep really did use the llm interface it would get an "access denied" error for all models at runtime, which... is correct, but the reason might be non-obvious. So yeah probably helpful to emit the same warning at build time as we would have done at add time. @fibonacci1729 what do you reckon? Effort?
There was a problem hiding this comment.
I think the best we could do at build time is check if a particular capability was inherited and issue a warning iff the configuration is the empty set.
There was a problem hiding this comment.
Hey all,
Thanks for working on this important part of the spin ecosystem.
I added a few comments, but most of them are nits and only few have to do with the underlying proposal/design, which in general I think is solid.
I'm quite new to spin (Akamai employee working with the Fermyon team) so I might have made some wrong assumptions/basic errors (e.g., not knowing how external dependency resolution error handling is done, not knowing if there's a way to remove added dependencies, ...). Feel free to correct me where wrong :)
One of the bigger missing pieces in this proposal as a higher-level user is the tie-in with WIT and auto-generation of programming language bindings when a dependency is added. I reckon that's documented elsewhere / seen as a separate project (which is fine), but I might have expected just a bit more tie-in to how that's supposed to interact here. Currently the text just talks about re-generating the spin-dependencies.wit file, but it's not entirely clear what that does exactly/how that flows into getting the dependency actually ready for use from within other components. If another command is needed (say... spin deps bind who knows) it would be good if that next step would e.g., be mentioned in the output text after a spin deps add potentially).
|
@rmarx Thanks for asking about how interfaces flow from For example, the Rust SDK has a macro that generates bindings from So all of that is out of scope for this SIP, but it's fair to say the reason it's out of scope is folk knowledge rather than explicitly documented. |
|
Here is test case as sample for how it will work in JS - https://github.com/spinframework/spin-js-sdk/tree/main/test/deps-test https://github.com/spinframework/spin-js-sdk/blob/643f08206354bc43e24ac52d60adc3b9822c559d/test/deps-test/package.json#L8-L10 is the important bit where we have a NPM script for generating the bindings that gets called with the build step first before building the component. |
|
Hey @itowlson and @karthik2804, thanks for the added context here :) While I of course still agree this is/should be a separate thing, I'm not entirely agreed with "that is out of scope for this SIP"... (though all I'm really asking for is a "Don't forget to run In one of the comments above where I asked about maybe needing a With Karthik's example above though (and hopefully correctly interpreting @itowlson), it's clear that the generation of the language bindings only happens during the build process. Put differently IIUC: after If this is correctly interpreted on my end, then I think we can make this clearer to the programmer by explicitly mentioning the recommendation to run To re-iterate, I think it's fine to do the binding generation etc. at build time. I'm just saying that it might be confusing to newer users that are unfamiliar with the ecosystem (and that are maybe just copy-pasting a TL;DR: if |
Well - and I apologise for the pedantry - it depends. But yes in the general case the user might need to build or refresh their IDE or whatever to get the bindings to regenerate. (The Rust language server is maddening in that in theory it can pick up changes without a build... but in practice it often doesn't pick up changes even after a build, because the build and language server are completely independent. Whereas in JS the IDE will never pick up without a build - barring watch shenanigans - but a build will reliably make them available in the IDE.) So we probably want a dreadfully vague and generic message along the lines of "you may need to build or reload the project to see your bindings." I hope that makes sense - I do sympathise with the confusion! |
Signed-off-by: Brian Hardock <brian.hardock@fermyon.com>
This SIP proposes a Dx for guiding a user into adding a dependency to a component in a Spin.toml. It builds on #3445 and the implementation #3450.