flatcar/nebraska

[RFE] Create client library to handle common updates workflow

Closed this issue · 2 comments

Current situation

Nebraska implements the Omaha protocol and thus any clients should be able to interact with it (i.e. perform update requests, and communicate the state back throughout the update workflow) using it.

Impact

This however requires knowledge of the Omaha protocol itself (what an update request gets as response, what state to send when an update is finished, etc.) and that can become a complex task in itself.

Ideal future situation

We want to lower the barrier for projects to start using Omaha-managed updates, and thus would like to have a library that requires the minimum configuration/code from clients in order to manage updates for a certain product.

For example, at a minimum, users should have to tell this library what the Nebraska/Omaha endpoint is + what to do with a downloaded update payload. All state communication/handling should be done with default settings by the library.

**Implementation options

We can start by making it a go package omahaclient github.com/nebraska/omahaclient/v1.

type updater struct {
  omahaclient.ContainerImageUpdater
}

func (* updater) Deploy(manifest *updater.UpdateManifest) omahaclient.Error {
  // Do whatever and then docker run again...
}

// Client initialization
ne := updater.New("https://myserver/app/io.org.App/")
ne.SetUpdater(updater)
ne.SetCurrentVersion("v0.1.2")
ne.SetChannel("beta")

ne.Start()

Maybe my go code above is not the best but hopefully gives the idea.

After ne.Start(), the updater starts performing update requests to the server. When an update exists, Nebraska responds with a URL and a metadata. Since we are composing a "container image updater", it's default "Download" implementation will pull + verify the new image, and then our implementation will stop a service and run the image again (we get details from the image in the UpdateManifest struct which is TBD).

Functionality we need

Even though Omaha can support several apps in one request, let's focus on one app request.
required means the update workflow cannot start unless we have this info.

Initialize:

  1. (required) Set the update endpoint: updater.New("https://myserver/app/io.org.App/v1/updates")
  2. Set the app id: updater.NewFromConfig("https://myserver/app/io.org.App/v1/updates", &updater.Config{AppID: "io.kinvolk.DockerContainerApp"}), may be also updaterInst.SetAppID("io.kinvolk.DockerContainerApp"), default is ""
  3. Set the channel: (same as above but with the channel info), default is ""
  4. Set the current version: (same as above but with the version), default is ""

Update start:

updater.Start() will set up a timer to check for updates from 5-15 mins initially, and after every failed attempt or finished update will be 45-90 mins.

(We should of course add an updater.Stop which will stop the update checker timer)

Let's worry about configuring the timer's details later.

Update workflow:

Every time an update check is triggered, and an update is available, then:

  1. Interpret the update response data and perform the GetUpdate step which by default will do the following, based on the update type:
    application/vnd.oci.image -> pull from the container image with the defined digest, from the defined registry
    binary -> download into a temporary location

otherwise -> fail because it's not implemented

  1. If the GetUpdate was successfully run, then call ApplyUpdate which needs to be implemented by the client
Error handling

Any "automatic" behavior (pulling an image, downloading a binary) should automatically report any errors to the server (Omaha events).

If a method implemented by the client returns an error, this error is also sent out to the server.

Besides, we should have updater.SendEvent(...) if more events (including errors) need to be sent.
For the event/error definitions, I guess we can rely on go-omaha's ones for now...

See also #420 .

Additional information

This was a rough example, maybe other basic functions related to authentication need to be done. Take it with a grain of sault.

Acceptance Criteria:

  • lib has documentation/examples
  • lib has testing
  • lib allows basic update workflow
  • lib has been reviewed/checked and approved by CNI

sounds great.

the requirements/outcome:

  • anything considered (like openapi/swagger) does not break the existing API used by all the Flatcar machines in the world
  • it's very intuitive for users to read and discover how to write code to either Consume from a channel, or publish new content to a channel, using the API and libraries
  • it's easy to manage for the project (write once, and generate; as opposed to having to make changes across code and docs manually)

I agree. Please also note that in my description above, it shouldn't affect the server code at all. I am currently writing another issue regarding how to cover different payload response types, and that's where we should be more careful.
For the admin/write CLI we can manage it in the #357 .