Triggering a "refresh" from inside a hook

I want to force an update of some of the already installed snaps in my system when I install/update a specific snap. The reasoning behind this is that I am using managed updates and I want to have the different snaps installed on sync in an easy way. All these tests have been carried out on a classic Ubuntu running in an armhf board.

I created a configure hook for the foo snap with the next contents:

#!/bin/sh -e

#Update seeded snaps
snap refresh bar
snap refresh baz
snap refresh core

Is this a correct approach? Results don’t seem to be consistent, ranging between these 4 cases:

  • The whole installation finishes successfully, installing the foo snap and launching the hook that refreshes the others. This is actually what I would expect in every install/refresh of the foo snap.
  • The foo snap installation finishes successfully, however, the hook is ignored and the bar, baz, core snaps are not refreshed.
  • Error during hook execution:

$ snap install foo --classic --dangerous
error: cannot perform the following tasks:
Run configure hook of “foo” snap if present (run hook “configure”: error: snap “core” has “install-snap” change in progress)

  • Error during hook execution:

$ snap install foo --classic --dangerous
error: cannot perform the following tasks:
Run configure hook of “foo” snap if present (run hook “configure”:
-----
bar 19.04-0.4 from mbeneto refreshed
baz 19.04-0.3 from mbeneto refreshed
2019-04-03T06:49:28Z INFO Waiting for restart…
2019-04-03T06:51:23Z INFO Waiting for restart…

<exceeded maximum runtime of 5m0s>
-----)

I tried with other hooks like post-refresh and install with similar results. Is this a supported feature or should it be tackled with another approach?

Although I came up with some workarounds for implementing the desired behaviour without using hooks, I would like to solve this using them. Would it be some more background appreciated?

without looking into the details of what you’re trying to do, at a first pass I’d mention:

  1. hooks have a hard time limit, and you have no control over how long a snap download will take, so waiting for it to finish in a hook seems very risky
  2. any snap operation can result in a conflict which needs to be handled

(1) means you should really be using --no-wait, but (2) means actually you might be better off using the api directly, as there a conflict is easier to spot (a 409 status).

However retrying from a hook isn’t possible (because how would you force the hook to run again?), so really I’d suggest: have something not-in-a-hook do these things, and use the hook to trigger that.

HTH,

As you have noticed this is not well supported. It would be some work to make it function better, it was not designed for, so far. In general, having snaps so tightly coupled to require these enforced refreshes is not the sweet spot of the design, but you didn’t give a lot of details about what are the concrete reasons pushing for that in the present case.

The typical thought around managed updates is to have a coordinating/management snap separate and independent from the others snaps/functional snaps.

About the details of what is happening internally on top of what @chipaca mentioned:

As mentioned snap refresh core is synchronous but specifically it will also try to restart snapd at some point, OTOH snapd does a best effort to not kill hooks when asked to restart but hooks will eventually hit their timeout.

Also as things stand snapd cannot distinguish that the snap request is coming from a hook, so cannot pick a different response/strategy here. That’s why hooks usually interact with snapd over snapctl (there is no snapctl refresh of any kind atm though).

@chipaca and @pedronis thanks for your prompt reply. Now I understand the reasons why it was behaving like that and can rule out an incorrect hook usage.

Knowing that is not supported, I will go with the workaround I previously pointed out, leaving to the middleware the decision of updating the different snaps every time the foo snap is updated.

The main reason behind this is being able to seed our image with the most generic snaps possible:

  • core
  • gadget
  • bar -> This snap is not supposed to change much. It contains basic configuration scripts in charge of, among others, downloading foo, which contains the whole functionality of the robot.

I know we could simply seed foo and refresh it, but, since this is going to be used in production, I am concerned that as the changes between the seeded foo and the latest revision available in the store grow, it will end up forcing a full download and reinstallation, with a considerably different experience during the first set-up.

In other words, now is always taking around 10 minutes to download and install the latest foo snap. I prefer that, instead of now taking some seconds, a couple of minutes in some weeks and maybe the full 10 minutes or even more in some months.

Are these concerns justified?

this will definitely work fine if you use a brand store and the snapd-control interface … though you should not use a hook (as @chipaca said you’d hit timeouts) but simply a daemon script/go-app/whatever. we have several customers that use their brand store exacly like this … having a “management engine” snap that connects to snapd-control to install/remove/update snaps from their brand store.