Skip to content
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

Manifest V2 #1780

Merged
merged 10 commits into from
Oct 17, 2023
Merged

Manifest V2 #1780

merged 10 commits into from
Oct 17, 2023

Conversation

lann
Copy link
Collaborator

@lann lann commented Sep 19, 2023

"I have only made this PR larger because I have not had the time to make it smaller."

Implements SIP-005 as updated in #1753.

Some highlights:

  • Completely refactored spin-manifest
    • Application et al. are no more; there is only the AppManifest, which closely resembles to the parsed spin.toml
    • There is also an AppManifestV1. Backward compat is provided by spin_manifest::compat::v1_to_v2_app.
  • Mostly-rewritten spin-loader crate takes an AppManifest and converts it into a LockedApp, ready to execute
  • A new ui-testing crate, supporting expanded "UI" (a.k.a. "golden file") test suites for both spin-manifest and spin-loader

There are some (intentional) changes open for debate:

  • Validation of component IDs is now more strict, requiring them to be valid component model kebab-case. The v1->v2 conversion should make this mostly transparent (search component_id_from_string if you care), but there may be some IDs floating around that won't parse now.
  • Rules for files patterns have been relaxed a bit; you can now use the "placement" form for individual files, e.g. { source = "content/some.txt", destination = "/other.txt" }
  • Added a "triggers" key to locked app metadata which is currently redundant but should help with forward-compatibility for multiple-trigger apps
  • Probably others that I have already forgotten or haven't yet noticed

Still to do is the spin doctor manifest upgrade feature, though hopefully that is pretty simple application of the v1_to_v2_app code. There are examples of what that should look like in crates/manifest/tests/ui/v1/*.toml.v2

@lann lann force-pushed the manifest-v2 branch 5 times, most recently from ebe9287 to 463c86e Compare September 22, 2023 20:15
@lann lann changed the title WIP: manifest V2 Manifest V2 Sep 27, 2023
@lann lann force-pushed the manifest-v2 branch 14 times, most recently from 5a72369 to 2fe346d Compare October 13, 2023 20:37
@lann lann marked this pull request as ready for review October 13, 2023 20:37
@@ -458,6 +461,17 @@ impl AppSource {
}
}

impl Display for AppSource {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reads more like a Debug implementation than a Display one.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@vdice vdice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All I can contribute are a couple eagle-eyed comment fixes 😂

Also wondering what our strategy is around updating example and template app manifests to v2? Perhaps as a follow-up PR before the 2.0 release? (I suppose we'll want to coordinate with developer docs updates as well.)

@itowlson
Copy link
Collaborator

I set out to cause unrest had a play with this as part of trying out deployment, and found a couple of minor oddities. These are with the "overnight" version - I know you are still fixing things so these may already be done in your working copy! - and are nits for the sad path not problems with the "normal" flow.

type in app trigger settings?

I wrote:

[application.trigger.http]
bayes = "/"

It correctly erred, but the message was "Error: metadata error: invalid metadata value for "trigger": Error("unknown field bayes, expected type or base", line: 0, column: 0)". Seems like type shouldn't be allowed, but here we go:

[application.trigger.http]
type = "redis"
base = "/"

And it was perfectly happy with that. I wondered if the http part was giving a name to the trigger which was of type redis, but no, the trigger was still type http.

Mismatched application and component triggers

When I wrote redis in my application trigger instead of http:

[application.trigger.redis]
base = "/"

[[trigger.http]]
component = "hello3"
route = "/..."

...it ran without warning.

I am guessing the concept is that all triggers are available via the trigger table array, and that the application.trigger merely applies global settings to those you happen to use, and if you choose to apply global settings that are never used, then what of it? Still it felt a bit surprising, especially with a property that doesn't apply to Redis (since HTTP rejected unknown settings). I'll maybe try to get a better sense of the intended mental model at some point, so we can communicate this correctly.

Mismatched trigger and configuration

I wrote this (to explore a theory about names and types):

[application.trigger.redis]
type = "http"
base = "/"

[[trigger.redis]]
component = "hello3"
route = "/..."

It understandably errored, with the message:

Error: invalid trigger configuration for "redis-1"

Caused by:
   0: json error: unknown field `route`, expected one of `component`, `channel`, `executor`
   1: unknown field `route`, expected one of `component`, `channel`, `executor`

The name "redis-1" does not refer to anything in my TOML file, and it was puzzling to see a "JSON error" when I was working only with TOML.

All of these can absolutely be deferred to issues for after the happy-path functionality has landed - they're probably better than the corresponding manifest v1 errors...!

@lann
Copy link
Collaborator Author

lann commented Oct 17, 2023

Also wondering what our strategy is around updating example and template app manifests to v2? Perhaps as a follow-up PR before the 2.0 release? (I suppose we'll want to coordinate with developer docs updates as well.)

Yeah I want to make sure there aren't any major changes to the implementation first. There will be at least a couple of followups.

@lann
Copy link
Collaborator Author

lann commented Oct 17, 2023

These problems with global trigger config will probably require some refactoring of spin-trigger that I'm hoping to avoid in this already-massive PR.

The name "redis-1" does not refer to anything in my TOML file

Suggestions for improving this? The name is secretly meaningful in that it is assigned to the 1st redis trigger.

@vdice
Copy link
Contributor

vdice commented Oct 17, 2023

Suggestions for improving this? The name is secretly meaningful in that it is assigned to the 1st redis trigger.

Would it be possible to point to the line in the manifest/toml causing the error (as follow-up, if even feasible)?

@lann
Copy link
Collaborator Author

lann commented Oct 17, 2023

Would it be possible to point to the line in the manifest/toml causing the error (as follow-up, if even feasible)?

Its tricky currently because the trigger config gets validated by the trigger executor which only gets the locked app JSON. I have a couple of ideas for how to improve the error messages with varying degrees of accuracy and complexity, like injecting parse span information from the original manifest into the locked app metadata. If I can't come up with something that works reasonably well for all trigger types (including custom), I can at least (as a fallback) go back to special-casing validation of http triggers.

Copy link
Collaborator

@rylev rylev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!! I have some questions but mostly because I'm unfamiliar with a lot of this code.

Comment on lines +33 to 34
#[serde(default)]
pub id: String,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd don't love the pattern of deserializing with a defaulted, invalid field that gets updated later. I don't understand the code well enough to offer a suggestion and maybe this just the way it has to be, but if there's some way to rearrange it to not have to do this, that would be awesome.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be done differently with more code, but this bit is somewhat of hack to begin with, extracting partial data from a otherwise-possibly-invalid manifest.

0: Failed to load component `hello`
1: One or more allowed_http_hosts entries was invalid:
ftp://random-data-api.fermyon.app isn't a valid host or host:port string
example.com/wib/wob contains a path, should be host and optional port only
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this fail because of the lack of scheme? Perhaps we could make the error message clearer?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷 this is unchanged by this PR; I just added the test

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rylev The first one fails because there is a scheme (and it's not a HTTP scheme). The second fails because it contains a path, not just a host. Maybe they look too run-together at the moment which confuses matters? But as Lann says this error message already exists, I can noodle on it separately.

}

// Copy files matching glob pattern or single file/directory path.
async fn copy_glob_or_path(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: these copy methods might be best in a separate module.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They use self...

use ui_testing::{Failed, Normalizer, UiTestsRunner};

fn main() -> anyhow::Result<()> {
// Insert dummy wasm into cache to avoid network traffic
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused by this. Why would the network be hit if we don't add the dummy?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

valid-manifest.toml has a remote source which the loader would (try and fail to) download.

Comment on lines 7 to 9
/// Extracts each `ComponentSpec::Inline` into `AppManifest::component`s,
/// replacing it with a `ComponentSpec::Reference`.
pub fn normalize_inline_components(manifest: &mut AppManifest) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps I'm confused by the name ComponentSpec, but I'm confused by what this function and normalize_trigger_ids actually do a high-level (I get that they set some missing values but why?).

Copy link
Collaborator Author

@lann lann Oct 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes code that consumes manifests simpler by not needing to deal with the flexibility that these normalize.

lann added 9 commits October 17, 2023 13:04
Signed-off-by: Lann Martin <lann.martin@fermyon.com>
Signed-off-by: Lann Martin <lann.martin@fermyon.com>
Move spin-trigger UI tests to spin-loader.

Signed-off-by: Lann Martin <lann.martin@fermyon.com>
Signed-off-by: Lann Martin <lann.martin@fermyon.com>
Signed-off-by: Lann Martin <lann.martin@fermyon.com>
This collects some serde tools into a separate crate, avoiding some
inter-crate dependencies.

Signed-off-by: Lann Martin <lann.martin@fermyon.com>
Signed-off-by: Lann Martin <lann.martin@fermyon.com>
Signed-off-by: Lann Martin <lann.martin@fermyon.com>
Signed-off-by: Lann Martin <lann.martin@fermyon.com>
@itowlson
Copy link
Collaborator

@lann the "redis-1" error happens in this situation:

[[trigger.redis]]
component = "hello3"
route = "/..."

I suspect most users are going to think of that as 'the "hello3" trigger' or 'the "hello3" component'.

Signed-off-by: Lann Martin <lann.martin@fermyon.com>
@lann
Copy link
Collaborator Author

lann commented Oct 17, 2023

@itowlson I added some nicer ID normalization; see normalization.toml (and adjacent normalization.toml.norm).

@itowlson
Copy link
Collaborator

@lann Thanks. That feels more helpful. I have ideas for further heuristics on identifying unnamed components, but the sort of errors I was raising were the result of malicious tinkering - probably better to wait and see if people ever run into them in the wild, and iterate on them at that point.

@lann lann merged commit 57b004c into spinframework:main Oct 17, 2023
@lann lann deleted the manifest-v2 branch July 23, 2024 15:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants