Possible evolution path for Snap Store endpoints regarding Epoch

As part of the work to implement the Epoch functionality, we’re planning the migration from the current “single integer” model to the full epochs structure (two lists of integers, read and write).

From an endpoint and its functionality points of view, we need to consider which filtering is done server side, and the format of the epoch field in the response.

This post will present a solution for almost all endpoints, but there is an open case at the end about /api/v1/snaps/details/<name>, for which we need feedback to take the decision of what behaviour we want.

/v2/snaps/refresh

Nothing to do here, this endpoint supports full Epoch already, and all snapds hitting it already understand epochs properly, and server side proper filtering according to Epoch can be done.

/v2/snaps/info/<name>

There is no filtering server side in this case, and we can return the full structure (this endpoint is new, it was born defining the full structure in the API).

/api/v1/snaps/metadata

This endpoint was used in old snapds to refresh (new snapds use the v2 refresh/install), so we can take a simple solution here:

  • filter server side for epoch equal to 0 (which is current correct value for all snaps revisions)

  • return the epoch, which will be always 0, as it’s currently done (no format change, return just the number)

This behaviour (referred as the “backwards compatibility behaviour” through this text) will be sane for old snapds, and at the same time will provide an upgrade path so eventually the client will have new snapds with proper epochs support.

/api/v1/package/<name>, /api/v1/click-metadata, /api/v1/search, /api/v1/snaps, /api/v1/snaps/names

In these cases we can just apply the backwards compatibility behaviour, as these endpoints are not used by new snapds at all.

/api/v1/snaps/search

In this case the response will always have epoch in 0 (so it will work in any snapd), but there will not be any filtering server side, so hitting this endpoint the client would be able to see all packages (we would be maybe “lying” in the epoch value in some responses).

/api/v1/snaps/details/<name>

This is where taking a decision is complicated.

For sure we need to return an old-style response (single digit as string), otherwise we would be breaking old clients. The real decision is if we should apply any specific filter server side or not.

Possible actions:

  • avoid the client seeing packages with complex Epoch: we could filter server side, and if the package has epochs more complex than read=[0]/write=[0] return a 404 so the package is not accessed, but is unclear if that’s sane for 18.04 (and maybe 18.04.01) clients

  • let the client see all revisions for all packages, but “lying” in some cases about their epoch value: we could not apply any filter, as we do with /search, but we may be letting install things that doesn’t have an upgrade path “from 0”.

  • let the client see all revisions with epoch=0 for any package: in this case we just “filter out” revisions with complex epochs, returning a positive result for the client (but with information that may be really old). Note that this behaviour is the “backwards compatibility” one that we apply in other old endpoints. The problem here is that we’re allowing to install something that is old and may be insecure or have other problems.

Note that clients “snapd >= 2.32.4” could find or get info about snaps with modern epochs, as those clients would eventually hit the /refresh endpoint to install them (which is “modern epochs capable”) but also note that really modern snapds would hit new v2 /info endpoint instead of /details, so one simple solution for this may be to consider all snapds hitting /details as “old ones”.

1 Like

When we discussed this at length in a series of Hangouts, we reached the conclusion that the only way to present a sane, uniform view of the world to the different clients was by doing a little bit of user agent inspection until we moved everything to the new endpoints. This was seen as an unavoidable consequence of trying to shoehorn things into the old APIs that weren’t ready for it, but given time pressure, acceptable.

I see nothing of that in your description, as if we had never talked about it. If we’re going to disregard things we agreed on, I’d at least like to hear a rationale instead of this gaslighting approach.

I’m going to assume you have a good reason to avoid looking at the user agent (I still would like to hear it). So my response below assumes that.


Don’t lie. Lying will result in very surprising behaviour if somebody is trying to debug it.
Instead I’d suggest we don’t return the epoch in the fields for v1 search.

Either way this will result in an old (pre-2.29) snapd finding things in a search that it then can not install. Given that pre-2.29 is now old enough that it represents less than 0.25% of active installs, and that only snaps that use a structured epoch will be affected, I think this is OK.

If we had a v2/search already, I’d say limit all v1 to backwards compatibility behaviour. As we don’t, I think this is the best we can do for now.

If the server lies about epoch, and a snap with a structured epoch is returned to a pre-2.29 snapd (which includes the default snapd that ships in 16.04 (and 17.10, but that shouldn’t be a fresh install today), until that snapd is able to update itself either from repo or by downloading core), snapd will fail to install the snap. I don’t think that’s acceptable.

Note that this failure is about structured epochs, which includes an epoch that is equivalent to epoch: 0.

That is, if a snap has

epoch:
  read: [0]
  write: [0]

in its snap.yaml, a pre-2.29 snapd will not accept it as a valid snap. Also, pre-2.29 snapd understands epoch: 42 just fine. It’s not the epoch number but structured vs string epochs that’s the issue.

If details returns a 404 for snaps that have structured epochs, then any snapd between 2.29 and 2.32.4 that could have installed the snap will instead fail. Search will find it, but install won’t. Between 2.32.4 and ~2.34 just ‘snap info’ will fail, which is perhaps acceptable once enough time has passed. In any case it’s all rather messy.

The only option that doesn’t result in strange behaviour on the client is to only let the client see non-structured epochs. But for this to be consistent, all the (v1) endpoints need to have this filtering; otherwise there’ll be paths with surprising errors, or inconsistent versions obtained or described.


I think that, if you need to not use the UA string to decide what to do, the only sane thing is to filter by non-structured epochs, for all v1 endpoints. This includes search.

This does mean that we won’t really have epochs support until a v2 search gets done. When’s that going to happen?

@facundobatista @wgrant We need to have a meeting to discuss this again. We certainly have room for options, but none of them will include presenting wrong data and breaking down clients in bad ways. Let’s please meet and talk over a hangout.

User-Agent sniffing is pretty gross, particularly since the behaviour changed in a patch release. We should avoid it unless we have a very good reason to look at it.

If old snapds are happy with this, that’s a much better solution.

Is this materially different from a snap that uses an interface that isn’t supported by the release image’s snapd, or some other future incompatibility? Installing those can presumably fail too. If your stable channel uses a feature that doesn’t exist in an old snapd, your users can no longer install the stable channel on that old snapd. There’d be no version inconsistency, and we wouldn’t be giving new 18.04.0 clients old versions of snaps with remote root exploits just because the new version uses an epoch.

We met to discuss these compatibility aspects earlier today.

Here are are some notes from the call:

  • Invariant: we don’t want to break old snapd clients badly no matter what
  • It’s okay to say “please upgrade your snapd” when attempting to install an improper revision that has an unsupported epoch, though
  • We might not want people to be installing old revisions, perhaps compromised with old security issues, by limiting store responses to epoch zero snaps when talking to old snapds
  • It might be okay to do that because few snaps will use non-zero epochs immediately
  • We want “core” and “snapd” to be upgradable even when those snaps start to use epochs, though (otherwise we lock ourselves out of an upgrade path)
  • Interestingly, the earlier argument can be made on the other direction: given that few snaps will use non-zero epochs immediately, it’s okay and useful to implement backwards compatibility behavior for now by only returning snaps on the zero epoch when talking to old snapds - that’s the majority of snaps
  • The compatible behavior is to only let old snapds know about the sequence of epoch zero snaps; then, once a new snapd runs, it will see the remaining epochs available for relevant snaps and will move forward
  • For searching, we’ll stop returning the epoch; that might be seen as a backward incompatible change, but in practice the epoch field never had a meaning as it wasn’t implemented in the backend
  • On installation, if the snap found by the store doesn’t have an epoch zero and the store is talking to an old snapd, we return an error
  • Side note discussed: it’s okay for complex epochs to have holes… (read 2 and 4, but not 3); we’ve agreed that the corner case is so corner that it’s not worth special casing in the interest of safety

To close the loop about the endpoints that were to be defined yet:

  • For /api/v1/snaps/details/<name> we’ll apply the “backwards compatibility behaviour”, which now includes the detail that if a snap is requested that doesn’t have an epoch zero, it will be not found. Also, at some point in the future this endpoint may start to just return 404 if a package that have complex epochs is requested (will be discussed and properly notified).

  • For /api/v1/snaps/search we’ll stop returning the epoch field in the response, and will not do any filter server side regarding this.