updater

package module
v0.0.0-...-177cce2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 4, 2025 License: Apache-2.0 Imports: 11 Imported by: 1

Documentation

Overview

Package updater aims to simplify omaha-powered updates.

Its goal is to abstract many of the omaha-protocol details, so users can perform updates without having to understand the omaha protocal internals.

Since the omaha protocol is very powerful, it supports many options that are beyond the scope of this package. So the updater package assumes that there's only one single application involved in the update, and that the update is being performed for one single instance of that application.

It also simplifies the update information that is represented by an omaha response, but allows to retrieve the actual OmahaResponse object if needed.

Basics:

Each update has four main parts involved:

  1. The application: represents the software getting updated;
  2. The instance: represents the instance of the application that's getting updated.
  3. The instance version: this is used by the server to decide how to respond (whether there's a new version available or not).
  4. The channel: an application may have different channels of releases (this is typical "beta", "stable", etc.).

The way omaha managed updates work is that omaha responds to update checks, and relies on the information given by the application's instance to keep track of each update's state. So the basic workflow for using this updater package is:

  1. Check for an update, if there's one available then. If there is not, try again later.
  2. Inform the server we're started it (this is done by sending a progress report of "download started").
  3. Actually perform the download or whatever represents fetching the update's parts.
  4. Inform the server that the download is finished it and that we're applying the update (this is done by sending a progress report of "installation started").
  5. Apply the update (this deeply depends on what each application does, but may involve extracting files into locations, migrating configuration, etc.).
  6. Inform the server that the update installation is finished; run the new version of the application and report that the update is now complete (these are two different progress reports and may involve running).

Note that if the update requires a restart, then there's a specific progress report for that. The caller is also responsible for keeping any local state the update implementation needs (like e.g. knowing that a restart has happened, or that the version if now running).

Index

Examples

Constants

View Source
const (
	ProgressDownloadStarted progress = iota
	ProgressDownloadFinished
	ProgressInstallationStarted
	ProgressInstallationFinished
	ProgressUpdateComplete
	ProgressUpdateCompleteAndRestarted
	ProgressError
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	OmahaURL        string
	AppID           string
	Channel         string
	InstanceID      string
	InstanceVersion string
	Debug           bool
	OmahaReqHandler OmahaRequestHandler
}

Config is used to configure new updater instance.

type HTTPDoer

type HTTPDoer interface {
	Do(*http.Request) (*http.Response, error)
}

HTTPDoer interface allows the user to create their custom implementation to handle proxies, retries etc.

type NoUpdateError

type NoUpdateError struct {
	AppID        string
	Channel      string
	UpdateStatus string
}

NoUpdateError is returned by TryUpdate when no update is available for app.

func (NoUpdateError) Error

func (e NoUpdateError) Error() string

type OmahaRequestHandler

type OmahaRequestHandler interface {
	Handle(ctx context.Context, url string, req *omaha.Request) (*omaha.Response, error)
}

OmahaRequestHandler wraps the Handle function which takes in context, url, omaha.Request and returns omaha.Response.

func NewOmahaRequestHandler

func NewOmahaRequestHandler(client HTTPDoer) OmahaRequestHandler

NewOmahaRequestHandler returns a OmahaRequestHandler which uses the HTTPDoer client to handle the post request to the Omaha server. If nil is passed, then http.DefaultClient is used.

type UpdateHandler

type UpdateHandler interface {
	FetchUpdate(ctx context.Context, info UpdateInfo) error
	ApplyUpdate(ctx context.Context, info UpdateInfo) error
}

UpdateHandler is an interface that wraps the FetchUpdate and ApplyUpdate command. Both FetchUpdate and ApplyUpdate take context and UpdateInfo as args and must return non-nil error if fetching or applying the update fails.

type UpdateInfo

type UpdateInfo struct {
	HasUpdate    bool
	Version      string
	UpdateStatus string
	AppID        string
	URLs         []string
	Packages     []*omaha.Package
	// contains filtered or unexported fields
}

UpdateInfo wraps helper functions and fields to fetch specific values from the omaha response that was recieved for check if any new update exists request.

func (*UpdateInfo) OmahaResponse

func (u *UpdateInfo) OmahaResponse() *omaha.Response

OmahaReponse returns the raw omaha response.

func (*UpdateInfo) Package

func (u *UpdateInfo) Package() *omaha.Package

Package returns the first package from the omaha response, returns nil if the package is not present in the omaha response.

func (*UpdateInfo) URL

func (u *UpdateInfo) URL() string

URL returns the first update URL in the omaha response, returns "" if the URL is not present in the omaha response.

type Updater

type Updater interface {
	// SendOmahaRequest sends raw Omaha request provided to the Omaha server.
	SendOmahaRequest(ctx context.Context, req *omaha.Request) (*omaha.Response, error)
	// SendOmahaEvent sends Omaha event request to the Omaha server.
	SendOmahaEvent(ctx context.Context, event *omaha.EventRequest) (*omaha.Response, error)
	// CheckForUpdates checks if there are new updated versions for the application.
	CheckForUpdates(ctx context.Context) (*UpdateInfo, error)
	// ReportProgress reports progress of update to the Omaha server.
	ReportProgress(ctx context.Context, progress progress) error
	// ReportError reports errors with custom error code.
	ReportError(ctx context.Context, errorCode *int) error
	// CompleteUpdate takes the UpdateInfo, updates the application
	// version with the UpdateInfo version and reports ProgressUpdateComplete to
	// the Omaha server.
	CompleteUpdate(ctx context.Context, info *UpdateInfo) error
	// TryUpdate function takes an implementation of UpdateHandler
	// and runs the complete flow from checking updates to reporting update status.
	TryUpdate(ctx context.Context, handler UpdateHandler) error
	// InstanceVersion returns the current version of the application.
	InstanceVersion() string
	// SetInstanceVersion takes a string and sets it as the application version.
	SetInstanceVersion(version string)
}

Updater interface wraps functions required to update an application

Example

ExampleUpdater shows how to use the updater package to update an application manually.

package main

import (
	"context"
	"fmt"

	"github.com/google/uuid"
	"github.com/kinvolk/nebraska/updater"
)

func someFunctionThatDownloadsAFile(ctx context.Context, url string) (string, error) {
	// Download file logic goes here
	return "/tmp/downloads/examplefile.txt", nil
}

func someFunctionThatExtractsTheUpdateAndInstallIt(ctx context.Context, filePath string) error {
	// Extract and install update logic goes here
	return nil
}

// ExampleUpdater shows how to use the updater package to
// update an application manually.
func main() error {
	conf := updater.Config{
		OmahaURL:        "http://test.omahaserver.com/v1/update/",
		AppID:           "application_id",
		Channel:         "stable",
		InstanceID:      uuid.NewString(),
		InstanceVersion: "0.0.1",
	}

	appUpdater, err := updater.New(conf)
	if err != nil {
		return fmt.Errorf("init updater: %w", err)
	}

	ctx := context.TODO()

	updateInfo, err := appUpdater.CheckForUpdates(ctx)
	if err != nil {
		return fmt.Errorf("checking updates for app: %q, err: %w", conf.AppID, err)
	}

	if !updateInfo.HasUpdate {
		return fmt.Errorf("No update exists for the application")
	}

	// So we got an update, let's report we'll start downloading it.
	if err := appUpdater.ReportProgress(ctx, updater.ProgressDownloadStarted); err != nil {
		if progressErr := appUpdater.ReportError(ctx, nil); progressErr != nil {
			fmt.Println("Reporting progress error:", progressErr)
		}
		return fmt.Errorf("reporting download started: %w", err)
	}

	// This should be implemented by the caller.
	filePath, err := someFunctionThatDownloadsAFile(ctx, updateInfo.URL())
	if err != nil {
		// Oops something went wrong.
		if progressErr := appUpdater.ReportError(ctx, nil); progressErr != nil {
			fmt.Println("reporting error:", progressErr)
		}
		return fmt.Errorf("downloading update: %w", err)
	}

	// The download was successful, let's inform that to the Omaha server.
	if err := appUpdater.ReportProgress(ctx, updater.ProgressDownloadFinished); err != nil {
		if progressErr := appUpdater.ReportError(ctx, nil); progressErr != nil {
			fmt.Println("Reporting progress error:", progressErr)
		}
		return fmt.Errorf("reporting download finished: %w", err)
	}

	// We got our update file, let's install it!
	if err := appUpdater.ReportProgress(ctx, updater.ProgressInstallationStarted); err != nil {
		if progressErr := appUpdater.ReportError(ctx, nil); progressErr != nil {
			fmt.Println("reporting progress error:", progressErr)
		}
		return fmt.Errorf("reporting installation started: %w", err)
	}

	// This should be your own implementation.
	if err := someFunctionThatExtractsTheUpdateAndInstallIt(ctx, filePath); err != nil {
		// Oops something went wrong.
		if progressErr := appUpdater.ReportError(ctx, nil); progressErr != nil {
			fmt.Println("Reporting error:", progressErr)
		}
		return fmt.Errorf("applying update: %w", err)
	}

	if err := appUpdater.CompleteUpdate(ctx, updateInfo); err != nil {
		if progressErr := appUpdater.ReportError(ctx, nil); progressErr != nil {
			fmt.Println("reporting progress error:", progressErr)
		}
		return fmt.Errorf("reporting complete update: %w", err)
	}

	return nil
}
Output:

Example (WithUpdateHandler)

ExampleUpdater_withUpdateHandler shows how to use the updater package to update an application automatically using exampleUpdateHandler.

package main

import (
	"context"
	"fmt"

	"github.com/google/uuid"
	"github.com/kinvolk/nebraska/updater"
)

type exampleUpdateHandler struct{}

func (e exampleUpdateHandler) FetchUpdate(ctx context.Context, info updater.UpdateInfo) error {
	// download, err := someFunctionThatDownloadsAFile(ctx, info.GetURL())
	// if err != nil {
	// 	return err
	// }
	return nil
}

func (e exampleUpdateHandler) ApplyUpdate(ctx context.Context, info updater.UpdateInfo) error {
	// err := someFunctionThatExtractsTheUpdateAndInstallIt(ctx, getDownloadFile(ctx))
	// if err != nil {
	// 	// Oops something went wrong
	// 	return err
	// }

	// err := someFunctionThatExitsAndRerunsTheApp(ctx)
	// if err != nil {
	// 	// Oops something went wrong
	// 	return err
	// }
	return nil
}

// ExampleUpdater_withUpdateHandler shows how to use the updater package to
// update an application automatically using exampleUpdateHandler.
func main() error {
	conf := updater.Config{
		OmahaURL:        "http://test.omahaserver.com/v1/update/",
		AppID:           "application_id",
		Channel:         "stable",
		InstanceID:      uuid.NewString(),
		InstanceVersion: "0.0.1",
	}

	appUpdater, err := updater.New(conf)
	if err != nil {
		return fmt.Errorf("init updater: %w", err)
	}

	ctx := context.TODO()

	if err := appUpdater.TryUpdate(ctx, exampleUpdateHandler{}); err != nil {
		return fmt.Errorf("trying update: %w", err)
	}

	return nil
}
Output:

func New

func New(config Config) (Updater, error)

New takes config and returns Updater and error, returns an error if OmahaURL in the config is invalid.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL