Compare commits
No commits in common. "master" and "fixup-travis" have entirely different histories.
master
...
fixup-trav
44 changed files with 377 additions and 3847 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1 @@
|
|||
pkg
|
||||
bin
|
||||
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- 1.7.5
|
||||
- 1.8.1
|
||||
go: 1.1
|
||||
|
||||
script:
|
||||
- go test -v ./...
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
# How to Contribute
|
||||
|
||||
This project is [Apache 2.0 licensed](LICENSE) and accept contributions via
|
||||
GitHub-like pull requests. This document outlines some of the conventions on
|
||||
development workflow, commit message formatting, contact points and other
|
||||
resources to make it easier to get your contribution accepted.
|
||||
|
||||
# Certificate of Origin
|
||||
|
||||
By contributing to this project you agree to the Developer Certificate of
|
||||
Origin (DCO). This document was created by the Linux Kernel community and is a
|
||||
simple statement that you, as a contributor, have the legal right to make the
|
||||
contribution. See the [DCO](DCO) file for details.
|
||||
|
||||
## Getting Started
|
||||
|
||||
- Fork the repository
|
||||
- Read the [README](README.md) for build and test instructions
|
||||
- Play with the project, submit bugs, submit patches!
|
||||
|
||||
## Contribution Flow
|
||||
|
||||
This is a rough outline of what a contributor's workflow looks like:
|
||||
|
||||
- Create a topic branch from where you want to base your work (usually master).
|
||||
- Make commits of logical units.
|
||||
- Make sure your commit messages are in the proper format (see below).
|
||||
- Push your changes to a topic branch in your fork of the repository.
|
||||
- Make sure the tests pass, and add any new tests as appropriate.
|
||||
- Submit a pull request to the original repository.
|
||||
|
||||
Thanks for your contributions!
|
||||
|
||||
### Format of the Commit Message
|
||||
|
||||
We follow a rough convention for commit messages that is designed to answer two
|
||||
questions: what changed and why. The subject line should feature the what and
|
||||
the body of the commit should describe the why.
|
||||
|
||||
```
|
||||
scripts: add the test-cluster command
|
||||
|
||||
this uses tmux to setup a test cluster that you can easily kill and
|
||||
start for debugging.
|
||||
|
||||
Fixes #38
|
||||
```
|
||||
|
||||
The format can be described more formally as follows:
|
||||
|
||||
```
|
||||
<subsystem>: <what changed>
|
||||
<BLANK LINE>
|
||||
<why this change was made>
|
||||
<BLANK LINE>
|
||||
<footer>
|
||||
```
|
||||
|
||||
The first line is the subject and should be no longer than 70 characters, the
|
||||
second line is always blank, and other lines should be wrapped at 80 characters.
|
||||
This allows the message to be easier to read on GitHub as well as in various
|
||||
git tools.
|
36
DCO
36
DCO
|
@ -1,36 +0,0 @@
|
|||
Developer Certificate of Origin
|
||||
Version 1.1
|
||||
|
||||
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||
660 York Street, Suite 102,
|
||||
San Francisco, CA 94110 USA
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
8
LICENSE
8
LICENSE
|
@ -1,4 +1,5 @@
|
|||
Apache License
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
|
@ -178,7 +179,7 @@ Apache License
|
|||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
|
@ -186,7 +187,7 @@ Apache License
|
|||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -199,4 +200,3 @@ Apache License
|
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
|
24
Makefile
24
Makefile
|
@ -1,24 +0,0 @@
|
|||
# kernel-style V=1 build verbosity
|
||||
ifeq ("$(origin V)", "command line")
|
||||
BUILD_VERBOSE = $(V)
|
||||
endif
|
||||
|
||||
ifeq ($(BUILD_VERBOSE),1)
|
||||
Q =
|
||||
else
|
||||
Q = @
|
||||
endif
|
||||
|
||||
.PHONY: all
|
||||
all: bin/serve-package
|
||||
|
||||
bin/serve-package:
|
||||
$(Q)go build -o $@ cmd/serve-package/main.go
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
$(Q)rm -rf bin
|
||||
|
||||
.PHONY: vendor
|
||||
vendor:
|
||||
$(Q)go mod vendor
|
5
NOTICE
5
NOTICE
|
@ -1,5 +0,0 @@
|
|||
CoreOS Project
|
||||
Copyright 2017 CoreOS, Inc
|
||||
|
||||
This product includes software developed at CoreOS, Inc.
|
||||
(http://www.coreos.com/).
|
72
README.md
72
README.md
|
@ -1,71 +1,5 @@
|
|||
# Go Omaha
|
||||
Implementation of the omaha protocol in Go.
|
||||
|
||||
[![GoDoc](https://godoc.org/git.thisco.de/vbatts/go-omaha/omaha?status.svg)](https://godoc.org/git.thisco.de/vbatts/go-omaha/omaha)
|
||||
https://code.google.com/p/omaha/
|
||||
|
||||
Implementation of the [omaha update protocol](https://github.com/google/omaha) in Go.
|
||||
|
||||
## Status
|
||||
|
||||
This code is targeted for use with CoreOS's [CoreUpdate](https://coreos.com/products/coreupdate/) product and the Container Linux [update_engine](https://github.com/coreos/update_engine).
|
||||
As a result this is not a complete implementation of the [protocol](https://github.com/google/omaha/blob/master/doc/ServerProtocolV3.md) and inherits a number of quirks from update_engine.
|
||||
These differences include:
|
||||
|
||||
- No offline activity tracking.
|
||||
The protocol's ping mechanism allows for tracking application usage, reporting the number of days since the last ping and how many of those days saw active usage.
|
||||
CoreUpdate does not use this, instead assuming update clients are always online and checking in once every ~45-50 minutes.
|
||||
Clients not actively updating should send only a ping, indicating CoreUpdate's "Instance-Hold" state.
|
||||
Clients requesting an update should send a ping, update check, and an UpdateComplete:SuccessReboot event indicating CoreUpdate's "Complete" state.
|
||||
|
||||
- Various protocol extensions/abuses.
|
||||
update_engine, likely due to earlier limitations of the protocol and Google's server implementation, uses a number of non-standard fields.
|
||||
For example, packing a lot of extra attributes such as the package's SHA-256 hash into a "postinstall" action.
|
||||
As much as possible the code includes comments about these extensions.
|
||||
|
||||
- Many data fields not used by CoreUpdate are omitted.
|
||||
|
||||
## `serve-package`
|
||||
|
||||
This project includes a very simple program designed to serve a single Container Linux package on the local host. It is intended to be used as a manual updater for a machine that is not able to use a full-fledged CoreUpdate instance. Binaries are available for each released version on the [releases page](https://git.thisco.de/vbatts/go-omaha/releases). `serve-package` can also be built from source using the provided Makefile:
|
||||
|
||||
```bash
|
||||
make
|
||||
```
|
||||
|
||||
The binary will be available in the `bin/` folder.
|
||||
|
||||
It is recommended that the server be run directly on the machine you intend to update. Go to the [Container Linux release notes](https://coreos.com/releases/) and find the version number for the release you would like to update to. The update payload can be retrieved from
|
||||
|
||||
```
|
||||
https://update.release.core-os.net/amd64-usr/<version>/update.gz
|
||||
```
|
||||
|
||||
where `<version>` is the version number you retrieved from the releases page. For example, `https://update.release.core-os.net/amd64-usr/1576.4.0/update.gz` is the payload required to update to Container Linux version 1576.4.0.
|
||||
|
||||
Copy the update payload and the `serve-package` binary to the server you would like to update. `serve-package` can be run as follows:
|
||||
|
||||
```bash
|
||||
./serve-package --package-file update.gz --package-version <version>
|
||||
```
|
||||
|
||||
By default, the server listens on `localhost:8000`. This can be modified using the `--listen-address` option.
|
||||
|
||||
Next, `update_engine` needs to be configured to use the local server that was just set up:
|
||||
|
||||
```bash
|
||||
echo "SERVER=http://localhost:8000/v1/update" | sudo tee -a /etc/coreos/update.conf
|
||||
```
|
||||
|
||||
Restart `update_engine` and tell it to check for an update:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart update-engine.service
|
||||
update_engine_client -check_for_update
|
||||
```
|
||||
|
||||
If `locksmithd.service` is running, the machine will restart once it has updated to the latest version. Otherwise, watch the logs from `update-engine.service` to determine when the update is complete and the machine is ready to restart:
|
||||
|
||||
```bash
|
||||
journalctl -u update-engine.service -f
|
||||
# wait for a line that says "Update successfully applied, waiting for reboot"
|
||||
sudo systemctl reboot
|
||||
```
|
||||
[![Build Status](https://travis-ci.org/coreos/go-omaha.png)](https://travis-ci.org/coreos/go-omaha)
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.thisco.de/vbatts/go-omaha/omaha"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pkgfile := flag.String("package-file", "", "Path to the update payload")
|
||||
version := flag.String("package-version", "", "Semantic version of the package provided")
|
||||
listenAddress := flag.String("listen-address", ":8000", "Host and IP to listen on")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *pkgfile == "" {
|
||||
fmt.Println("package-file is a required flag")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if *version == "" {
|
||||
fmt.Println("package-version is a required flag")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
server, err := omaha.NewTrivialServer(*listenAddress)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to make new server: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
server.SetVersion(*version)
|
||||
err = server.AddPackage(*pkgfile, "update.gz")
|
||||
if err != nil {
|
||||
fmt.Printf("failed to add package: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = server.Serve()
|
||||
if err != nil {
|
||||
fmt.Printf("server exited with an error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
## Kinvolk Community Code of Conduct
|
||||
|
||||
### Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, and in the interest of
|
||||
fostering an open and welcoming community, we pledge to respect all people who
|
||||
contribute through reporting issues, posting feature requests, updating
|
||||
documentation, submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free
|
||||
experience for everyone, regardless of level of experience, gender, gender
|
||||
identity and expression, sexual orientation, disability, personal appearance,
|
||||
body size, race, ethnicity, age, religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as physical or electronic addresses, without explicit permission
|
||||
* Other unethical or unprofessional conduct.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct. By adopting this Code of Conduct,
|
||||
project maintainers commit themselves to fairly and consistently applying these
|
||||
principles to every aspect of managing this project. Project maintainers who do
|
||||
not follow or enforce the Code of Conduct may be permanently removed from the
|
||||
project team.
|
||||
|
||||
This code of conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting a project maintainer, Vincent Batts <vbatts@thisco.de>
|
||||
|
||||
This Code of Conduct is adapted from the Contributor Covenant
|
||||
(http://contributor-covenant.org), version 1.2.0, available at
|
||||
http://contributor-covenant.org/version/1/2/0/
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<request protocol="3.0" version="ChromeOSUpdateEngine-0.1.0.0" updaterversion="ChromeOSUpdateEngine-0.1.0.0" installsource="ondemandupdate" ismachine="1">
|
||||
<os version="Indy" platform="Chrome OS" sp="ForcedUpdate_x86_64"></os>
|
||||
<app appid="{87efface-864d-49a5-9bb3-4b050a7c227a}" bootid="{7D52A1CC-7066-40F0-91C7-7CB6A871BFDE}" machineid="{8BDE4C4D-9083-4D61-B41C-3253212C0C37}" oem="ec3000" version="ForcedUpdate" track="dev-channel" from_track="developer-build" lang="en-US" board="amd64-generic" hardware_class="" delta_okay="false" >
|
||||
<app appid="{87efface-864d-49a5-9bb3-4b050a7c227a}" version="ForcedUpdate" track="" from_track="developer-build" lang="en-US" board="amd64-generic" hardware_class="" delta_okay="false" >
|
||||
<ping active="1" a="-1" r="-1"></ping>
|
||||
<updatecheck targetversionprefix=""></updatecheck>
|
||||
<event eventtype="3" eventresult="2" previousversion=""></event>
|
||||
|
|
9
go.mod
9
go.mod
|
@ -1,9 +0,0 @@
|
|||
module git.thisco.de/vbatts/go-omaha
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/blang/semver v3.5.1+incompatible
|
||||
github.com/kylelemons/godebug v1.1.0
|
||||
github.com/satori/go.uuid v1.1.0
|
||||
)
|
8
go.sum
8
go.sum
|
@ -1,8 +0,0 @@
|
|||
git.thisco.de/vbatts/go-omaha v0.0.0-20180327202221-e409d983eb60 h1:lqjtEFY5RgNgWJWOv0KUW0Llvh0+Ved7nWIqFEssY34=
|
||||
git.thisco.de/vbatts/go-omaha v0.0.0-20180327202221-e409d983eb60/go.mod h1:w+Vl1DisYu3l2h88jvqZDV414oSIcTY8YrZLOa/vLME=
|
||||
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/satori/go.uuid v1.1.0 h1:B9KXyj+GzIpJbV7gmr873NsY6zpbxNy24CBtGrk7jHo=
|
||||
github.com/satori/go.uuid v1.1.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
|
@ -1,368 +0,0 @@
|
|||
// Copyright 2017 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package client provides a general purpose Omaha update client implementation.
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
uuid "github.com/satori/go.uuid"
|
||||
|
||||
"git.thisco.de/vbatts/go-omaha/omaha"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultClientVersion = "go-omaha"
|
||||
|
||||
// periodic update check and ping intervals
|
||||
pingFuzz = 10 * time.Minute
|
||||
pingDelay = 7 * time.Minute // first check after 2-12 minutes
|
||||
pingInterval = 45 * time.Minute // check in every 40-50 minutes
|
||||
)
|
||||
|
||||
// Client supports managing multiple apps using a single server.
|
||||
type Client struct {
|
||||
apiClient *httpClient
|
||||
apiEndpoint string
|
||||
clientVersion string
|
||||
userID string
|
||||
sessionID string
|
||||
isMachine bool
|
||||
sentPing bool
|
||||
apps map[string]*AppClient
|
||||
}
|
||||
|
||||
// AppClient supports managing a single application.
|
||||
type AppClient struct {
|
||||
*Client
|
||||
appID string
|
||||
track string
|
||||
version string
|
||||
oem string
|
||||
}
|
||||
|
||||
// New creates an omaha client for updating one or more applications.
|
||||
// userID must be a persistent unique identifier of this update client.
|
||||
func New(serverURL, userID string) (*Client, error) {
|
||||
if userID == "" {
|
||||
return nil, errors.New("omaha: empty user identifier")
|
||||
}
|
||||
|
||||
c := &Client{
|
||||
apiClient: newHTTPClient(),
|
||||
clientVersion: defaultClientVersion,
|
||||
userID: userID,
|
||||
sessionID: uuid.NewV4().String(),
|
||||
apps: make(map[string]*AppClient),
|
||||
}
|
||||
|
||||
if err := c.SetServerURL(serverURL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// SetServerURL changes the Omaha server this client talks to.
|
||||
// If the URL does not include a path component /v1/update/ is assumed.
|
||||
func (c *Client) SetServerURL(serverURL string) error {
|
||||
u, err := url.Parse(serverURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("omaha: invalid server URL: %v", err)
|
||||
}
|
||||
if u.Scheme != "http" && u.Scheme != "https" {
|
||||
return fmt.Errorf("omaha: invalid server protocol: %s", u)
|
||||
}
|
||||
if u.Host == "" {
|
||||
return fmt.Errorf("omaha: invalid server host: %s", u)
|
||||
}
|
||||
if u.Path == "" || u.Path == "/" {
|
||||
u.Path = "/v1/update/"
|
||||
}
|
||||
|
||||
c.apiEndpoint = u.String()
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetClientVersion sets the identifier of this updater application.
|
||||
// e.g. "update_engine-0.1.0". Default is "go-omaha".
|
||||
func (c *Client) SetClientVersion(clientVersion string) {
|
||||
c.clientVersion = clientVersion
|
||||
}
|
||||
|
||||
// NextPing returns a timer channel that will fire when the next update
|
||||
// check or ping should be sent.
|
||||
func (c *Client) NextPing() <-chan time.Time {
|
||||
d := pingDelay
|
||||
if c.sentPing {
|
||||
d = pingInterval
|
||||
}
|
||||
return FuzzyAfter(d, pingFuzz)
|
||||
}
|
||||
|
||||
// AppClient gets the application client for the given application ID.
|
||||
func (c *Client) AppClient(appID string) (*AppClient, error) {
|
||||
if app, ok := c.apps[appID]; ok {
|
||||
return app, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("omaha: missing app client %q", appID)
|
||||
}
|
||||
|
||||
// NewAppClient creates a new application client.
|
||||
func (c *Client) NewAppClient(appID, appVersion string) (*AppClient, error) {
|
||||
if _, ok := c.apps[appID]; ok {
|
||||
return nil, fmt.Errorf("omaha: duplicate app client %q", appID)
|
||||
}
|
||||
|
||||
ac := &AppClient{
|
||||
Client: c,
|
||||
appID: appID,
|
||||
}
|
||||
c.apps[appID] = ac
|
||||
|
||||
return ac, nil
|
||||
}
|
||||
|
||||
// NewAppClient creates a single application client.
|
||||
// Shorthand for New(serverURL, userID).NewAppClient(appID, appVersion).
|
||||
func NewAppClient(serverURL, userID, appID, appVersion string) (*AppClient, error) {
|
||||
c, err := New(serverURL, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ac, err := c.NewAppClient(appID, appVersion)
|
||||
if err := ac.SetVersion(appVersion); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ac, nil
|
||||
}
|
||||
|
||||
func (ac *AppClient) SetAppID(appID string) error {
|
||||
if appID == ac.appID {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, ok := ac.apps[appID]; ok {
|
||||
return fmt.Errorf("omaha: duplicate app %q", appID)
|
||||
}
|
||||
|
||||
delete(ac.apps, ac.appID)
|
||||
ac.appID = appID
|
||||
ac.apps[appID] = ac
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetVersion changes the application version.
|
||||
func (ac *AppClient) SetVersion(version string) error {
|
||||
if version == "" {
|
||||
return errors.New("omaha: empty application version")
|
||||
}
|
||||
|
||||
ac.version = version
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTrack sets the application update track or group.
|
||||
// This is a update_engine/Core Update protocol extension.
|
||||
func (ac *AppClient) SetTrack(track string) error {
|
||||
// Although track is an omaha extension and theoretically not required
|
||||
// our Core Update server requires track to be set to a valid id/name.
|
||||
// TODO: deprecate track and use the standard cohort protocol fields.
|
||||
if track == "" {
|
||||
return errors.New("omaha: empty application update track/group")
|
||||
}
|
||||
|
||||
ac.track = track
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetOEM sets the application OEM name.
|
||||
// This is a update_engine/Core Update protocol extension.
|
||||
func (ac *AppClient) SetOEM(oem string) {
|
||||
ac.oem = oem
|
||||
}
|
||||
|
||||
func (ac *AppClient) UpdateCheck() (*omaha.UpdateResponse, error) {
|
||||
req := ac.NewAppRequest()
|
||||
app := req.Apps[0]
|
||||
app.AddPing()
|
||||
app.AddUpdateCheck()
|
||||
|
||||
// Tell CoreUpdate to consider us in its "Complete" state,
|
||||
// otherwise it interprets ping as "Instance-Hold" which is
|
||||
// nonsense when we are sending an update check!
|
||||
app.Events = append(app.Events, EventComplete)
|
||||
|
||||
ac.sentPing = true
|
||||
|
||||
appResp, err := ac.SendAppRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// BUG: CoreUpdate does not send ping status in response.
|
||||
/*if appResp.Ping == nil {
|
||||
ac.Event(NewErrorEvent(ExitCodeOmahaResponseInvalid))
|
||||
return nil, fmt.Errorf("omaha: ping status missing from response")
|
||||
}
|
||||
|
||||
if appResp.Ping.Status != "ok" {
|
||||
return nil, fmt.Errorf("omaha: ping status %s", appResp.Ping.Status)
|
||||
}*/
|
||||
|
||||
if appResp.UpdateCheck == nil {
|
||||
ac.Event(NewErrorEvent(ExitCodeOmahaResponseInvalid))
|
||||
return nil, fmt.Errorf("omaha: update check missing from response")
|
||||
}
|
||||
|
||||
if appResp.UpdateCheck.Status != omaha.UpdateOK {
|
||||
return nil, appResp.UpdateCheck.Status
|
||||
}
|
||||
|
||||
return appResp.UpdateCheck, nil
|
||||
}
|
||||
|
||||
func (ac *AppClient) Ping() error {
|
||||
req := ac.NewAppRequest()
|
||||
app := req.Apps[0]
|
||||
app.AddPing()
|
||||
|
||||
ac.sentPing = true
|
||||
|
||||
appResp, err := ac.SendAppRequest(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// BUG: CoreUpdate does not send ping status in response.
|
||||
_ = appResp
|
||||
/*if appResp.Ping == nil {
|
||||
ac.Event(NewErrorEvent(ExitCodeOmahaResponseInvalid))
|
||||
return fmt.Errorf("omaha: ping status missing from response")
|
||||
}
|
||||
|
||||
if appResp.Ping.Status != "ok" {
|
||||
return fmt.Errorf("omaha: ping status %s", appResp.Ping.Status)
|
||||
}*/
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Event asynchronously sends the given omaha event.
|
||||
// Reading the error channel is optional.
|
||||
func (ac *AppClient) Event(event *omaha.EventRequest) <-chan error {
|
||||
errc := make(chan error, 1)
|
||||
url := ac.apiEndpoint
|
||||
req := ac.NewAppRequest()
|
||||
app := req.Apps[0]
|
||||
app.Events = append(app.Events, event)
|
||||
|
||||
go func() {
|
||||
appResp, err := ac.doReq(url, req)
|
||||
if err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
|
||||
// BUG: CoreUpdate does not send event status in response.
|
||||
_ = appResp
|
||||
/*if len(appResp.Events) == 0 {
|
||||
errc <- fmt.Errorf("omaha: event status missing from response")
|
||||
return
|
||||
}
|
||||
|
||||
if appResp.Events[0].Status != "ok" {
|
||||
errc <- fmt.Errorf("omaha: event status %s", appResp.Events[0].Status)
|
||||
return
|
||||
}*/
|
||||
|
||||
errc <- nil
|
||||
return
|
||||
}()
|
||||
|
||||
return errc
|
||||
}
|
||||
|
||||
// NewAppRequest creates a Request object containing one application.
|
||||
func (ac *AppClient) NewAppRequest() *omaha.Request {
|
||||
req := omaha.NewRequest()
|
||||
req.Version = ac.clientVersion
|
||||
req.UserID = ac.userID
|
||||
req.SessionID = ac.sessionID
|
||||
if ac.isMachine {
|
||||
req.IsMachine = 1
|
||||
}
|
||||
|
||||
app := req.AddApp(ac.appID, ac.version)
|
||||
app.Track = ac.track
|
||||
app.OEM = ac.oem
|
||||
|
||||
// MachineID and BootID are non-standard fields used by CoreOS'
|
||||
// update_engine and Core Update. Copy their values from the
|
||||
// standard UserID and SessionID. Eventually the non-standard
|
||||
// fields should be deprecated.
|
||||
app.MachineID = req.UserID
|
||||
app.BootID = req.SessionID
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
// SendAppRequest sends a Request object and validates the response.
|
||||
// On failure an error event is automatically sent to the server.
|
||||
func (ac *AppClient) SendAppRequest(req *omaha.Request) (*omaha.AppResponse, error) {
|
||||
resp, err := ac.doReq(ac.apiEndpoint, req)
|
||||
if _, ok := err.(omaha.AppStatus); ok {
|
||||
// No point to sending an error if we got a well-formed
|
||||
// non-ok application status in the response.
|
||||
} else if err, ok := err.(ErrorEvent); ok {
|
||||
ac.Event(err.ErrorEvent())
|
||||
} else if err != nil {
|
||||
ac.Event(NewErrorEvent(ExitCodeOmahaRequestError))
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// doReq posts an omaha request. It may be called in its own goroutine so
|
||||
// it should not touch any mutable data in AppClient, but apiClient is ok.
|
||||
func (ac *AppClient) doReq(url string, req *omaha.Request) (*omaha.AppResponse, error) {
|
||||
if len(req.Apps) != 1 {
|
||||
panic(fmt.Errorf("unexpected number of apps: %d", len(req.Apps)))
|
||||
}
|
||||
appID := req.Apps[0].ID
|
||||
resp, err := ac.apiClient.Omaha(url, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
appResp := resp.GetApp(appID)
|
||||
if appResp == nil {
|
||||
return nil, &omahaError{
|
||||
Err: fmt.Errorf("app %s missing from response", appID),
|
||||
Code: ExitCodeOmahaResponseInvalid,
|
||||
}
|
||||
}
|
||||
|
||||
if appResp.Status != omaha.AppOK {
|
||||
return nil, appResp.Status
|
||||
}
|
||||
|
||||
return appResp, nil
|
||||
}
|
|
@ -1,199 +0,0 @@
|
|||
// Copyright 2017 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"git.thisco.de/vbatts/go-omaha/omaha"
|
||||
)
|
||||
|
||||
// implements omaha.Updater
|
||||
type recorder struct {
|
||||
t *testing.T
|
||||
update *omaha.Update
|
||||
checks []*omaha.UpdateRequest
|
||||
events []*omaha.EventRequest
|
||||
pings []*omaha.PingRequest
|
||||
}
|
||||
|
||||
func newRecordingServer(t *testing.T, u *omaha.Update) (*recorder, *omaha.Server) {
|
||||
r := &recorder{t: t, update: u}
|
||||
s, err := omaha.NewServer("127.0.0.1:0", r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
go s.Serve()
|
||||
return r, s
|
||||
}
|
||||
|
||||
func (r *recorder) CheckApp(req *omaha.Request, app *omaha.AppRequest) error {
|
||||
// CheckApp is meant for checking if app.ID is valid but we don't
|
||||
// care and accept any ID. Instead this is just a convenient place
|
||||
// to check that all requests are well formed.
|
||||
if len(req.SessionID) != 36 {
|
||||
r.t.Errorf("SessionID %q is not a UUID", req.SessionID)
|
||||
}
|
||||
if app.BootID != req.SessionID {
|
||||
r.t.Errorf("BootID %q != SessionID %q", app.BootID, req.SessionID)
|
||||
}
|
||||
if req.UserID == "" {
|
||||
r.t.Error("UserID is blank")
|
||||
}
|
||||
if app.MachineID != req.UserID {
|
||||
r.t.Errorf("MachineID %q != UserID %q", app.MachineID, req.UserID)
|
||||
}
|
||||
if app.Version == "" {
|
||||
r.t.Error("App Version is blank")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *recorder) CheckUpdate(req *omaha.Request, app *omaha.AppRequest) (*omaha.Update, error) {
|
||||
r.checks = append(r.checks, app.UpdateCheck)
|
||||
if r.update == nil {
|
||||
return nil, omaha.NoUpdate
|
||||
} else {
|
||||
return r.update, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *recorder) Event(req *omaha.Request, app *omaha.AppRequest, event *omaha.EventRequest) {
|
||||
r.events = append(r.events, event)
|
||||
}
|
||||
|
||||
func (r *recorder) Ping(req *omaha.Request, app *omaha.AppRequest) {
|
||||
r.pings = append(r.pings, app.Ping)
|
||||
}
|
||||
|
||||
func TestClientNoUpdate(t *testing.T) {
|
||||
r, s := newRecordingServer(t, nil)
|
||||
defer s.Destroy()
|
||||
|
||||
url := "http://" + s.Addr().String()
|
||||
ac, err := NewAppClient(url, "client-id", "app-id", "0.0.0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := ac.UpdateCheck(); err != omaha.NoUpdate {
|
||||
t.Fatalf("UpdateCheck id not return NoUpdate: %v", err)
|
||||
}
|
||||
|
||||
if len(r.pings) != 1 {
|
||||
t.Fatalf("expected 1 ping, not %d", len(r.pings))
|
||||
}
|
||||
|
||||
if len(r.checks) != 1 {
|
||||
t.Fatalf("expected 1 update check, not %d", len(r.checks))
|
||||
}
|
||||
|
||||
if len(r.events) != 1 {
|
||||
t.Fatalf("expected 1 event, not %d", len(r.events))
|
||||
}
|
||||
|
||||
if r.events[0].Type != omaha.EventTypeUpdateComplete ||
|
||||
r.events[0].Result != omaha.EventResultSuccessReboot {
|
||||
t.Fatalf("expected %#v, not %#v", EventComplete, r.events[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientWithUpdate(t *testing.T) {
|
||||
r, s := newRecordingServer(t, &omaha.Update{
|
||||
Manifest: omaha.Manifest{
|
||||
Version: "1.1.1",
|
||||
},
|
||||
})
|
||||
defer s.Destroy()
|
||||
|
||||
url := "http://" + s.Addr().String()
|
||||
ac, err := NewAppClient(url, "client-id", "app-id", "0.0.0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
update, err := ac.UpdateCheck()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if update.Manifest.Version != "1.1.1" {
|
||||
t.Fatalf("expected version 1.1.1, not %s", update.Manifest.Version)
|
||||
}
|
||||
|
||||
if len(r.pings) != 1 {
|
||||
t.Fatalf("expected 1 ping, not %d", len(r.pings))
|
||||
}
|
||||
|
||||
if len(r.checks) != 1 {
|
||||
t.Fatalf("expected 1 update check, not %d", len(r.checks))
|
||||
}
|
||||
|
||||
if len(r.events) != 1 {
|
||||
t.Fatalf("expected 1 event, not %d", len(r.events))
|
||||
}
|
||||
|
||||
if r.events[0].Type != omaha.EventTypeUpdateComplete ||
|
||||
r.events[0].Result != omaha.EventResultSuccessReboot {
|
||||
t.Fatalf("expected %#v, not %#v", EventComplete, r.events[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientPing(t *testing.T) {
|
||||
r, s := newRecordingServer(t, nil)
|
||||
defer s.Destroy()
|
||||
|
||||
url := "http://" + s.Addr().String()
|
||||
ac, err := NewAppClient(url, "client-id", "app-id", "0.0.0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := ac.Ping(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(r.pings) != 1 {
|
||||
t.Fatalf("expected 1 ping, not %d", len(r.pings))
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientEvent(t *testing.T) {
|
||||
r, s := newRecordingServer(t, nil)
|
||||
defer s.Destroy()
|
||||
|
||||
url := "http://" + s.Addr().String()
|
||||
ac, err := NewAppClient(url, "client-id", "app-id", "0.0.0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
event := &omaha.EventRequest{
|
||||
Type: omaha.EventTypeDownloadComplete,
|
||||
Result: omaha.EventResultSuccess,
|
||||
}
|
||||
if err := <-ac.Event(event); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(r.events) != 1 {
|
||||
t.Fatalf("expected 1 event, not %d", len(r.events))
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(event, r.events[0]) {
|
||||
t.Fatalf("sent != received:\n%#v\n%#v", event, r.events[0])
|
||||
}
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
// Copyright 2017 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.thisco.de/vbatts/go-omaha/omaha"
|
||||
)
|
||||
|
||||
var (
|
||||
bodySizeError = &omahaError{
|
||||
Err: errors.New("http response exceeded 1MB"),
|
||||
Code: ExitCodeOmahaResponseInvalid,
|
||||
}
|
||||
bodyEmptyError = &omahaError{
|
||||
Err: errors.New("http response was empty"),
|
||||
Code: ExitCodeOmahaRequestEmptyResponseError,
|
||||
}
|
||||
|
||||
// default parameters for expNetBackoff
|
||||
backoffStart = time.Second
|
||||
backoffTries = 7
|
||||
)
|
||||
|
||||
// retries and exponentially backs off for temporary network errors
|
||||
func expNetBackoff(f func() error) error {
|
||||
var (
|
||||
backoff = backoffStart
|
||||
tries = backoffTries
|
||||
)
|
||||
for {
|
||||
err := f()
|
||||
tries--
|
||||
if tries <= 0 {
|
||||
return err
|
||||
}
|
||||
if neterr, ok := err.(net.Error); !ok || !neterr.Temporary() {
|
||||
return err
|
||||
}
|
||||
FuzzySleep(backoff, backoff)
|
||||
backoff *= 2
|
||||
}
|
||||
}
|
||||
|
||||
// xml doesn't return the standard io.ErrUnexpectedEOF so check for both.
|
||||
func isUnexpectedEOF(err error) bool {
|
||||
if xerr, ok := err.(*xml.SyntaxError); ok {
|
||||
return xerr.Msg == "unexpected EOF"
|
||||
}
|
||||
return err == io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
// omahaError implements error and ErrorEvent for omaha requests/responses.
|
||||
type omahaError struct {
|
||||
Err error
|
||||
Code ExitCode
|
||||
}
|
||||
|
||||
func (oe *omahaError) Error() string {
|
||||
return "omaha: request failed: " + oe.Err.Error()
|
||||
}
|
||||
|
||||
func (oe *omahaError) ErrorEvent() *omaha.EventRequest {
|
||||
return NewErrorEvent(oe.Code)
|
||||
}
|
||||
|
||||
// httpError implements error, net.Error, and ErrorEvent for http responses.
|
||||
type httpError struct {
|
||||
*http.Response
|
||||
}
|
||||
|
||||
func (he *httpError) Error() string {
|
||||
return "http error: " + he.Status
|
||||
}
|
||||
|
||||
func (he *httpError) ErrorEvent() *omaha.EventRequest {
|
||||
code := ExitCodeOmahaRequestError
|
||||
if he.StatusCode > 0 && he.StatusCode < 1000 {
|
||||
code = ExitCodeOmahaRequestHTTPResponseBase + ExitCode(he.StatusCode)
|
||||
}
|
||||
return NewErrorEvent(code)
|
||||
}
|
||||
|
||||
func (he *httpError) Timeout() bool {
|
||||
switch he.StatusCode {
|
||||
case http.StatusRequestTimeout: // 408
|
||||
return true
|
||||
case http.StatusGatewayTimeout: // 504
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (he *httpError) Temporary() bool {
|
||||
if he.Timeout() {
|
||||
return true
|
||||
}
|
||||
switch he.StatusCode {
|
||||
case http.StatusTooManyRequests: // 429
|
||||
return true
|
||||
case http.StatusInternalServerError: // 500
|
||||
return true
|
||||
case http.StatusBadGateway: // 502
|
||||
return true
|
||||
case http.StatusServiceUnavailable: // 503
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
// Copyright 2017 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// use quicker backoff for testing
|
||||
backoffStart = time.Millisecond
|
||||
backoffTries = 3
|
||||
}
|
||||
|
||||
type tmpErr struct{}
|
||||
|
||||
func (e tmpErr) Error() string { return "fake temporary error" }
|
||||
func (e tmpErr) Temporary() bool { return true }
|
||||
func (e tmpErr) Timeout() bool { return false }
|
||||
|
||||
func TestExpNetBackoff(t *testing.T) {
|
||||
tries := 0
|
||||
err := expNetBackoff(func() error {
|
||||
tries++
|
||||
if tries < 2 {
|
||||
return tmpErr{}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if tries != 2 {
|
||||
t.Errorf("unexpected # of tries: %d", tries)
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
// Copyright 2017 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
//"os/signal"
|
||||
"syscall"
|
||||
|
||||
"git.thisco.de/vbatts/go-omaha/omaha"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
// Launch a dummy server for our client to talk to.
|
||||
s, err := omaha.NewTrivialServer("127.0.0.1:0")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer s.Destroy()
|
||||
go s.Serve()
|
||||
|
||||
// Configure our client. userID should be random but preserved
|
||||
// across restarts. version is the current version of our app.
|
||||
var (
|
||||
serverURL = "http://" + s.Addr().String()
|
||||
userID = "8b10fc6d-30ca-49b2-b1a2-8185f03d522b"
|
||||
appID = "5ca607f8-61b5-4692-90ce-30380ba05a98"
|
||||
version = "1.0.0"
|
||||
)
|
||||
c, err := NewAppClient(serverURL, userID, appID, version)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Client version is the name and version of this updater.
|
||||
c.SetClientVersion("example-0.0.1")
|
||||
|
||||
// Use SIGUSR1 to trigger immediate update checks.
|
||||
sigc := make(chan os.Signal, 1)
|
||||
//signal.Notify(sigc, syscall.SIGUSR1)
|
||||
sigc <- syscall.SIGUSR1 // Fake it
|
||||
|
||||
//for {
|
||||
var source string
|
||||
select {
|
||||
case <-sigc:
|
||||
source = "ondemandupdate"
|
||||
case <-c.NextPing():
|
||||
source = "scheduler"
|
||||
}
|
||||
|
||||
// TODO: pass source to UpdateCheck
|
||||
_ = source
|
||||
// If updates are disabled call c.Ping() instead.
|
||||
update, err := c.UpdateCheck()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
//continue
|
||||
return
|
||||
}
|
||||
|
||||
// Download new application version.
|
||||
c.Event(&omaha.EventRequest{
|
||||
Type: omaha.EventTypeUpdateDownloadFinished,
|
||||
Result: omaha.EventResultSuccess,
|
||||
})
|
||||
|
||||
// Install new application version here.
|
||||
c.Event(&omaha.EventRequest{
|
||||
Type: omaha.EventTypeUpdateComplete,
|
||||
Result: omaha.EventResultSuccess,
|
||||
})
|
||||
|
||||
// Restart, new application is now running.
|
||||
c.SetVersion(update.Manifest.Version)
|
||||
c.Event(&omaha.EventRequest{
|
||||
Type: omaha.EventTypeUpdateComplete,
|
||||
Result: omaha.EventResultSuccessReboot,
|
||||
})
|
||||
|
||||
//}
|
||||
|
||||
// Output:
|
||||
// omaha: update status noupdate
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
// Copyright 2017 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Ensure seeding the prng is never forgotten, that would defeat
|
||||
// the whole point of using fuzzy timers to guard against a DoS.
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// FuzzyDuration randomizes the duration d within the range specified
|
||||
// by fuzz. Specifically the value range is: [d-(fuzz/2), d+(fuzz/2)]
|
||||
// The result will never be negative.
|
||||
func FuzzyDuration(d, fuzz time.Duration) time.Duration {
|
||||
if fuzz < 0 {
|
||||
return d
|
||||
}
|
||||
// apply range [-fuzz/2, fuzz/2]
|
||||
d += time.Duration(rand.Int63n(int64(fuzz)+1) - (int64(fuzz) / 2))
|
||||
if d < 0 {
|
||||
return 0
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// FuzzyAfter waits for the fuzzy duration to elapse and then sends the
|
||||
// current time on the returned channel. See FuzzyDuration.
|
||||
func FuzzyAfter(d, fuzz time.Duration) <-chan time.Time {
|
||||
return time.After(FuzzyDuration(d, fuzz))
|
||||
}
|
||||
|
||||
// FuzzySleep pauses the current goroutine for the fuzzy duration d.
|
||||
// See FuzzyDuration.
|
||||
func FuzzySleep(d, fuzz time.Duration) {
|
||||
time.Sleep(FuzzyDuration(d, fuzz))
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
// Copyright 2017 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFuzzyDuration(t *testing.T) {
|
||||
const d = time.Minute
|
||||
for i := 0; i < 1000; i++ {
|
||||
f := FuzzyDuration(d, d)
|
||||
if f < d/2 {
|
||||
t.Errorf("%d < %d", f, d/2)
|
||||
} else if f > d+d/2 {
|
||||
t.Errorf("%d > %d", f, d+d/2)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
// Copyright 2017 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.thisco.de/vbatts/go-omaha/omaha"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTimeout = 90 * time.Second
|
||||
)
|
||||
|
||||
// httpClient extends the standard http.Client to support xml encoding
|
||||
// and decoding as well as automatic retries on transient failures.
|
||||
type httpClient struct {
|
||||
http.Client
|
||||
}
|
||||
|
||||
func newHTTPClient() *httpClient {
|
||||
return &httpClient{http.Client{
|
||||
Timeout: defaultTimeout,
|
||||
}}
|
||||
}
|
||||
|
||||
// doPost sends a single HTTP POST, returning a parsed omaha response.
|
||||
func (hc *httpClient) doPost(url string, reqBody []byte) (*omaha.Response, error) {
|
||||
resp, err := hc.Post(url, "text/xml; charset=utf-8", bytes.NewReader(reqBody))
|
||||
if err != nil {
|
||||
return nil, &omahaError{err, ExitCodeOmahaRequestError}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// A response over 1M in size is certainly bogus.
|
||||
respBody := &io.LimitedReader{R: resp.Body, N: 1024 * 1024}
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
omahaResp, err := omaha.ParseResponse(contentType, respBody)
|
||||
|
||||
// Report a more sensible error if we truncated the body.
|
||||
if isUnexpectedEOF(err) && respBody.N <= 0 {
|
||||
err = bodySizeError
|
||||
} else if err == io.EOF {
|
||||
err = bodyEmptyError
|
||||
} else if err != nil {
|
||||
err = &omahaError{err, ExitCodeOmahaRequestXMLParseError}
|
||||
}
|
||||
|
||||
// Prefer reporting HTTP errors over XML parsing errors.
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err = &httpError{resp}
|
||||
}
|
||||
|
||||
return omahaResp, err
|
||||
}
|
||||
|
||||
// Omaha encodes and sends an omaha request, retrying on any transient errors.
|
||||
func (hc *httpClient) Omaha(url string, req *omaha.Request) (resp *omaha.Response, err error) {
|
||||
buf := bytes.NewBufferString(xml.Header)
|
||||
enc := xml.NewEncoder(buf)
|
||||
if err := enc.Encode(req); err != nil {
|
||||
return nil, fmt.Errorf("omaha: failed to encode request: %v", err)
|
||||
}
|
||||
|
||||
expNetBackoff(func() error {
|
||||
resp, err = hc.doPost(url, buf.Bytes())
|
||||
return err
|
||||
})
|
||||
|
||||
return resp, err
|
||||
}
|
|
@ -1,212 +0,0 @@
|
|||
// Copyright 2017 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.thisco.de/vbatts/go-omaha/omaha"
|
||||
)
|
||||
|
||||
const (
|
||||
sampleRequest = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<request protocol="3.0" version="ChromeOSUpdateEngine-0.1.0.0" updaterversion="ChromeOSUpdateEngine-0.1.0.0" installsource="ondemandupdate" ismachine="1">
|
||||
<os version="Indy" platform="Chrome OS" sp="ForcedUpdate_x86_64"></os>
|
||||
<app appid="{87efface-864d-49a5-9bb3-4b050a7c227a}" bootid="{7D52A1CC-7066-40F0-91C7-7CB6A871BFDE}" machineid="{8BDE4C4D-9083-4D61-B41C-3253212C0C37}" oem="ec3000" version="ForcedUpdate" track="dev-channel" from_track="developer-build" lang="en-US" board="amd64-generic" hardware_class="" delta_okay="false" >
|
||||
<ping active="1" a="-1" r="-1"></ping>
|
||||
<updatecheck targetversionprefix=""></updatecheck>
|
||||
<event eventtype="3" eventresult="2" previousversion=""></event>
|
||||
</app>
|
||||
</request>
|
||||
`
|
||||
)
|
||||
|
||||
func TestHTTPClientDoPost(t *testing.T) {
|
||||
s, err := omaha.NewTrivialServer("127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Destroy()
|
||||
go s.Serve()
|
||||
|
||||
c := newHTTPClient()
|
||||
url := "http://" + s.Addr().String() + "/v1/update/"
|
||||
|
||||
resp, err := c.doPost(url, []byte(sampleRequest))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(resp.Apps) != 1 {
|
||||
t.Fatalf("Should be 1 app, not %d", len(resp.Apps))
|
||||
}
|
||||
if resp.Apps[0].Status != omaha.AppOK {
|
||||
t.Fatalf("Bad apps status: %q", resp.Apps[0].Status)
|
||||
}
|
||||
}
|
||||
|
||||
type flakyHandler struct {
|
||||
omaha.OmahaHandler
|
||||
flakes int
|
||||
reqs int
|
||||
}
|
||||
|
||||
func (f *flakyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
f.reqs++
|
||||
if f.flakes > 0 {
|
||||
f.flakes--
|
||||
http.Error(w, "Flake!", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
f.OmahaHandler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
type flakyServer struct {
|
||||
l net.Listener
|
||||
s *http.Server
|
||||
h *flakyHandler
|
||||
}
|
||||
|
||||
func newFlakyServer() (*flakyServer, error) {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f := &flakyServer{
|
||||
l: l,
|
||||
s: &http.Server{},
|
||||
h: &flakyHandler{
|
||||
OmahaHandler: omaha.OmahaHandler{
|
||||
Updater: omaha.UpdaterStub{},
|
||||
},
|
||||
flakes: 1,
|
||||
},
|
||||
}
|
||||
f.s.Handler = f.h
|
||||
|
||||
go f.s.Serve(l)
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func TestHTTPClientError(t *testing.T) {
|
||||
f, err := newFlakyServer()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.l.Close()
|
||||
|
||||
c := newHTTPClient()
|
||||
url := "http://" + f.l.Addr().String()
|
||||
|
||||
_, err = c.doPost(url, []byte(sampleRequest))
|
||||
switch err := err.(type) {
|
||||
case nil:
|
||||
t.Fatal("doPost succeeded but should have failed")
|
||||
case *httpError:
|
||||
if err.StatusCode != http.StatusInternalServerError {
|
||||
t.Fatalf("Unexpected http error: %v", err)
|
||||
}
|
||||
if err.Timeout() {
|
||||
t.Fatal("http 500 error reported as timeout")
|
||||
}
|
||||
if !err.Temporary() {
|
||||
t.Fatal("http 500 error not reported as temporary")
|
||||
}
|
||||
default:
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPClientRetry(t *testing.T) {
|
||||
f, err := newFlakyServer()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.l.Close()
|
||||
|
||||
req, err := omaha.ParseRequest("", strings.NewReader(sampleRequest))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
c := newHTTPClient()
|
||||
url := "http://" + f.l.Addr().String()
|
||||
|
||||
resp, err := c.Omaha(url, req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(resp.Apps) != 1 {
|
||||
t.Fatalf("Should be 1 app, not %d", len(resp.Apps))
|
||||
}
|
||||
|
||||
if resp.Apps[0].Status != omaha.AppOK {
|
||||
t.Fatalf("Bad apps status: %q", resp.Apps[0].Status)
|
||||
}
|
||||
|
||||
if f.h.reqs != 2 {
|
||||
t.Fatalf("Server received %d requests, not 2", f.h.reqs)
|
||||
}
|
||||
}
|
||||
|
||||
// should result in an unexected EOF
|
||||
func largeHandler1(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/xml; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?><response protocol="3.0">`))
|
||||
w.Write(bytes.Repeat([]byte{' '}, 2*1024*1024))
|
||||
w.Write([]byte(`</response>`))
|
||||
}
|
||||
|
||||
// should result in an EOF
|
||||
func largeHandler2(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/xml; charset=utf-8")
|
||||
w.Write(bytes.Repeat([]byte{' '}, 2*1024*1024))
|
||||
}
|
||||
|
||||
func TestHTTPClientLarge(t *testing.T) {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
s := &http.Server{
|
||||
Handler: http.HandlerFunc(largeHandler1),
|
||||
}
|
||||
go s.Serve(l)
|
||||
|
||||
c := newHTTPClient()
|
||||
url := "http://" + l.Addr().String()
|
||||
|
||||
_, err = c.doPost(url, []byte(sampleRequest))
|
||||
if err != bodySizeError {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// switch to failing before XML is read instead of half-way
|
||||
// through (which results in a different error internally)
|
||||
s.Handler = http.HandlerFunc(largeHandler2)
|
||||
|
||||
_, err = c.doPost(url, []byte(sampleRequest))
|
||||
if err != bodyEmptyError {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
// Copyright 2017 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build linux
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
const (
|
||||
machineIDPath = "/etc/machine-id"
|
||||
bootIDPath = "/proc/sys/kernel/random/boot_id"
|
||||
)
|
||||
|
||||
// NewMachineClient creates a machine-wide client, updating applications
|
||||
// that may be used by multiple users. On Linux the system's machine id
|
||||
// is used as the user id, and boot id is used as the omaha session id.
|
||||
func NewMachineClient(serverURL string) (*Client, error) {
|
||||
machineID, err := ioutil.ReadFile(machineIDPath)
|
||||
if err != nil {
|
||||
fmt.Errorf("omaha: failed to read machine id: %v", err)
|
||||
}
|
||||
|
||||
machineID = bytes.TrimSpace(machineID)
|
||||
// Although machineID should be a UUID, it is formatted as a
|
||||
// plain hex string, omitting the normal '-' separators, so it
|
||||
// should be 32 bytes long. It would be nice to reformat it to
|
||||
// add the '-' chars but update_engine doesn't so stick with its
|
||||
// behavior for now.
|
||||
if len(machineID) < 32 {
|
||||
fmt.Errorf("omaha: incomplete machine id: %q",
|
||||
machineID)
|
||||
}
|
||||
|
||||
bootID, err := ioutil.ReadFile(bootIDPath)
|
||||
if err != nil {
|
||||
fmt.Errorf("omaha: failed to read boot id: %v", err)
|
||||
}
|
||||
|
||||
bootID = bytes.TrimSpace(bootID)
|
||||
// unlike machineID, bootID *does* include '-' chars.
|
||||
if len(bootID) < 36 {
|
||||
fmt.Errorf("omaha: incomplete boot id: %q", bootID)
|
||||
}
|
||||
|
||||
c := &Client{
|
||||
apiClient: newHTTPClient(),
|
||||
clientVersion: defaultClientVersion,
|
||||
userID: string(machineID),
|
||||
sessionID: string(bootID),
|
||||
isMachine: true,
|
||||
apps: make(map[string]*AppClient),
|
||||
}
|
||||
|
||||
if err := c.SetServerURL(serverURL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
// Copyright 2017 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build linux
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// skip test if external file isn't readable
|
||||
func readOrSkip(t *testing.T, name string) string {
|
||||
data, err := ioutil.ReadFile(name)
|
||||
if err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
return string(bytes.TrimSpace(data))
|
||||
}
|
||||
|
||||
func TestNewMachine(t *testing.T) {
|
||||
userID := readOrSkip(t, machineIDPath)
|
||||
sessionID := readOrSkip(t, bootIDPath)
|
||||
|
||||
c, err := NewMachineClient("https://example.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c.userID != userID {
|
||||
t.Errorf("%q != %q", c.userID, userID)
|
||||
}
|
||||
if c.sessionID != sessionID {
|
||||
t.Errorf("%q != %q", c.sessionID, sessionID)
|
||||
}
|
||||
}
|
|
@ -1,226 +0,0 @@
|
|||
// Copyright 2017 CoreOS, Inc.
|
||||
// Copyright 2011 The Chromium OS Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.thisco.de/vbatts/go-omaha/omaha"
|
||||
)
|
||||
|
||||
var (
|
||||
// These events are what update_engine sends to CoreUpdate to
|
||||
// mark different steps in the update process.
|
||||
EventDownloading = &omaha.EventRequest{
|
||||
Type: omaha.EventTypeUpdateDownloadStarted,
|
||||
Result: omaha.EventResultSuccess,
|
||||
}
|
||||
EventDownloaded = &omaha.EventRequest{
|
||||
Type: omaha.EventTypeUpdateDownloadFinished,
|
||||
Result: omaha.EventResultSuccess,
|
||||
}
|
||||
EventInstalled = &omaha.EventRequest{
|
||||
Type: omaha.EventTypeUpdateComplete,
|
||||
Result: omaha.EventResultSuccess,
|
||||
}
|
||||
EventComplete = &omaha.EventRequest{
|
||||
Type: omaha.EventTypeUpdateComplete,
|
||||
Result: omaha.EventResultSuccessReboot,
|
||||
}
|
||||
)
|
||||
|
||||
// ExitCode is used for omaha event error codes derived from update_engine
|
||||
type ExitCode int
|
||||
|
||||
// These error codes are from CoreOS Container Linux update_engine 0.4.x
|
||||
// https://github.com/coreos/update_engine/blob/master/src/update_engine/action_processor.h
|
||||
// The whole list is included for the sake of completeness but lots of these
|
||||
// are not generally applicable and not even used by update_engine any more.
|
||||
// Also there are clearly duplicate errors for the same condition.
|
||||
const (
|
||||
ExitCodeSuccess ExitCode = 0
|
||||
ExitCodeError ExitCode = 1
|
||||
ExitCodeOmahaRequestError ExitCode = 2
|
||||
ExitCodeOmahaResponseHandlerError ExitCode = 3
|
||||
ExitCodeFilesystemCopierError ExitCode = 4
|
||||
ExitCodePostinstallRunnerError ExitCode = 5
|
||||
ExitCodeSetBootableFlagError ExitCode = 6
|
||||
ExitCodeInstallDeviceOpenError ExitCode = 7
|
||||
ExitCodeKernelDeviceOpenError ExitCode = 8
|
||||
ExitCodeDownloadTransferError ExitCode = 9
|
||||
ExitCodePayloadHashMismatchError ExitCode = 10
|
||||
ExitCodePayloadSizeMismatchError ExitCode = 11
|
||||
ExitCodeDownloadPayloadVerificationError ExitCode = 12
|
||||
ExitCodeDownloadNewPartitionInfoError ExitCode = 13
|
||||
ExitCodeDownloadWriteError ExitCode = 14
|
||||
ExitCodeNewRootfsVerificationError ExitCode = 15
|
||||
ExitCodeNewKernelVerificationError ExitCode = 16
|
||||
ExitCodeSignedDeltaPayloadExpectedError ExitCode = 17
|
||||
ExitCodeDownloadPayloadPubKeyVerificationError ExitCode = 18
|
||||
ExitCodePostinstallBootedFromFirmwareB ExitCode = 19
|
||||
ExitCodeDownloadStateInitializationError ExitCode = 20
|
||||
ExitCodeDownloadInvalidMetadataMagicString ExitCode = 21
|
||||
ExitCodeDownloadSignatureMissingInManifest ExitCode = 22
|
||||
ExitCodeDownloadManifestParseError ExitCode = 23
|
||||
ExitCodeDownloadMetadataSignatureError ExitCode = 24
|
||||
ExitCodeDownloadMetadataSignatureVerificationError ExitCode = 25
|
||||
ExitCodeDownloadMetadataSignatureMismatch ExitCode = 26
|
||||
ExitCodeDownloadOperationHashVerificationError ExitCode = 27
|
||||
ExitCodeDownloadOperationExecutionError ExitCode = 28
|
||||
ExitCodeDownloadOperationHashMismatch ExitCode = 29
|
||||
ExitCodeOmahaRequestEmptyResponseError ExitCode = 30
|
||||
ExitCodeOmahaRequestXMLParseError ExitCode = 31
|
||||
ExitCodeDownloadInvalidMetadataSize ExitCode = 32
|
||||
ExitCodeDownloadInvalidMetadataSignature ExitCode = 33
|
||||
ExitCodeOmahaResponseInvalid ExitCode = 34
|
||||
ExitCodeOmahaUpdateIgnoredPerPolicy ExitCode = 35
|
||||
ExitCodeOmahaUpdateDeferredPerPolicy ExitCode = 36
|
||||
ExitCodeOmahaErrorInHTTPResponse ExitCode = 37
|
||||
ExitCodeDownloadOperationHashMissingError ExitCode = 38
|
||||
ExitCodeDownloadMetadataSignatureMissingError ExitCode = 39
|
||||
ExitCodeOmahaUpdateDeferredForBackoff ExitCode = 40
|
||||
ExitCodePostinstallPowerwashError ExitCode = 41
|
||||
ExitCodeNewPCRPolicyVerificationError ExitCode = 42
|
||||
ExitCodeNewPCRPolicyHTTPError ExitCode = 43
|
||||
|
||||
// Use the 2xxx range to encode HTTP errors from the Omaha server.
|
||||
// Sometimes aggregated into ExitCodeOmahaErrorInHTTPResponse
|
||||
ExitCodeOmahaRequestHTTPResponseBase ExitCode = 2000 // + HTTP response code
|
||||
)
|
||||
|
||||
func (e ExitCode) String() string {
|
||||
switch e {
|
||||
case ExitCodeSuccess:
|
||||
return "success"
|
||||
case ExitCodeError:
|
||||
return "error"
|
||||
case ExitCodeOmahaRequestError:
|
||||
return "omaha request error"
|
||||
case ExitCodeOmahaResponseHandlerError:
|
||||
return "omaha response handler error"
|
||||
case ExitCodeFilesystemCopierError:
|
||||
return "filesystem copier error"
|
||||
case ExitCodePostinstallRunnerError:
|
||||
return "postinstall runner error"
|
||||
case ExitCodeSetBootableFlagError:
|
||||
return "set bootable flag error"
|
||||
case ExitCodeInstallDeviceOpenError:
|
||||
return "install device open error"
|
||||
case ExitCodeKernelDeviceOpenError:
|
||||
return "kernel device open error"
|
||||
case ExitCodeDownloadTransferError:
|
||||
return "download transfer error"
|
||||
case ExitCodePayloadHashMismatchError:
|
||||
return "payload hash mismatch error"
|
||||
case ExitCodePayloadSizeMismatchError:
|
||||
return "payload size mismatch error"
|
||||
case ExitCodeDownloadPayloadVerificationError:
|
||||
return "download payload verification error"
|
||||
case ExitCodeDownloadNewPartitionInfoError:
|
||||
return "download new partition info error"
|
||||
case ExitCodeDownloadWriteError:
|
||||
return "download write error"
|
||||
case ExitCodeNewRootfsVerificationError:
|
||||
return "new rootfs verification error"
|
||||
case ExitCodeNewKernelVerificationError:
|
||||
return "new kernel verification error"
|
||||
case ExitCodeSignedDeltaPayloadExpectedError:
|
||||
return "signed delta payload expected error"
|
||||
case ExitCodeDownloadPayloadPubKeyVerificationError:
|
||||
return "download payload pubkey verification error"
|
||||
case ExitCodePostinstallBootedFromFirmwareB:
|
||||
return "postinstall booted from firmware B"
|
||||
case ExitCodeDownloadStateInitializationError:
|
||||
return "download state initialization error"
|
||||
case ExitCodeDownloadInvalidMetadataMagicString:
|
||||
return "download invalid metadata magic string"
|
||||
case ExitCodeDownloadSignatureMissingInManifest:
|
||||
return "download signature missing in manifest"
|
||||
case ExitCodeDownloadManifestParseError:
|
||||
return "download manifest parse error"
|
||||
case ExitCodeDownloadMetadataSignatureError:
|
||||
return "download metadata signature error"
|
||||
case ExitCodeDownloadMetadataSignatureVerificationError:
|
||||
return "download metadata signature verification error"
|
||||
case ExitCodeDownloadMetadataSignatureMismatch:
|
||||
return "download metadata signature mismatch"
|
||||
case ExitCodeDownloadOperationHashVerificationError:
|
||||
return "download operation hash verification error"
|
||||
case ExitCodeDownloadOperationExecutionError:
|
||||
return "download operation execution error"
|
||||
case ExitCodeDownloadOperationHashMismatch:
|
||||
return "download operation hash mismatch"
|
||||
case ExitCodeOmahaRequestEmptyResponseError:
|
||||
return "omaha request empty response error"
|
||||
case ExitCodeOmahaRequestXMLParseError:
|
||||
return "omaha request XML parse error"
|
||||
case ExitCodeDownloadInvalidMetadataSize:
|
||||
return "download invalid metadata size"
|
||||
case ExitCodeDownloadInvalidMetadataSignature:
|
||||
return "download invalid metadata signature"
|
||||
case ExitCodeOmahaResponseInvalid:
|
||||
return "omaha response invalid"
|
||||
case ExitCodeOmahaUpdateIgnoredPerPolicy:
|
||||
return "omaha update ignored per policy"
|
||||
case ExitCodeOmahaUpdateDeferredPerPolicy:
|
||||
return "omaha update deferred per policy"
|
||||
case ExitCodeOmahaErrorInHTTPResponse:
|
||||
return "omaha error in HTTP response"
|
||||
case ExitCodeDownloadOperationHashMissingError:
|
||||
return "download operation hash missing error"
|
||||
case ExitCodeDownloadMetadataSignatureMissingError:
|
||||
return "download metadata signature missing error"
|
||||
case ExitCodeOmahaUpdateDeferredForBackoff:
|
||||
return "omaha update deferred for backoff"
|
||||
case ExitCodePostinstallPowerwashError:
|
||||
return "postinstall powerwash error"
|
||||
case ExitCodeNewPCRPolicyVerificationError:
|
||||
return "new PCR policy verification error"
|
||||
case ExitCodeNewPCRPolicyHTTPError:
|
||||
return "new PCR policy HTTP error"
|
||||
default:
|
||||
if e > ExitCodeOmahaRequestHTTPResponseBase {
|
||||
return fmt.Sprintf("omaha response HTTP %d error",
|
||||
e-ExitCodeOmahaRequestHTTPResponseBase)
|
||||
}
|
||||
return fmt.Sprintf("error code %d", e)
|
||||
}
|
||||
}
|
||||
|
||||
// NewErrorEvent creates an EventRequest for reporting errors.
|
||||
func NewErrorEvent(e ExitCode) *omaha.EventRequest {
|
||||
return &omaha.EventRequest{
|
||||
Type: omaha.EventTypeUpdateComplete,
|
||||
Result: omaha.EventResultError,
|
||||
ErrorCode: int(e),
|
||||
}
|
||||
}
|
||||
|
||||
// EventString allows for easily logging events in a readable format.
|
||||
func EventString(e *omaha.EventRequest) string {
|
||||
s := fmt.Sprintf("omaha event: %s: %s", e.Type, e.Result)
|
||||
if e.ErrorCode != 0 {
|
||||
s = fmt.Sprintf("%s (%d - %s)", s,
|
||||
e.ErrorCode, ExitCode(e.ErrorCode))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// ErrorEvent is an error type that can generate EventRequests for reporting.
|
||||
type ErrorEvent interface {
|
||||
error
|
||||
ErrorEvent() *omaha.EventRequest
|
||||
}
|
181
omaha/codes.go
181
omaha/codes.go
|
@ -1,181 +0,0 @@
|
|||
// Copyright 2013-2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package omaha
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type EventType int
|
||||
|
||||
const (
|
||||
EventTypeUnknown EventType = 0
|
||||
EventTypeDownloadComplete EventType = 1
|
||||
EventTypeInstallComplete EventType = 2
|
||||
EventTypeUpdateComplete EventType = 3
|
||||
EventTypeUninstall EventType = 4
|
||||
EventTypeDownloadStarted EventType = 5
|
||||
EventTypeInstallStarted EventType = 6
|
||||
EventTypeNewApplicationInstallStarted EventType = 9
|
||||
EventTypeSetupStarted EventType = 10
|
||||
EventTypeSetupFinished EventType = 11
|
||||
EventTypeUpdateApplicationStarted EventType = 12
|
||||
EventTypeUpdateDownloadStarted EventType = 13
|
||||
EventTypeUpdateDownloadFinished EventType = 14
|
||||
EventTypeUpdateInstallerStarted EventType = 15
|
||||
EventTypeSetupUpdateBegin EventType = 16
|
||||
EventTypeSetupUpdateComplete EventType = 17
|
||||
EventTypeRegisterProductComplete EventType = 20
|
||||
EventTypeOEMInstallFirstCheck EventType = 30
|
||||
EventTypeAppSpecificCommandStarted EventType = 40
|
||||
EventTypeAppSpecificCommandEnded EventType = 41
|
||||
EventTypeSetupFailure EventType = 100
|
||||
EventTypeComServerFailure EventType = 102
|
||||
EventTypeSetupUpdateFailure EventType = 103
|
||||
)
|
||||
|
||||
func (e EventType) String() string {
|
||||
switch e {
|
||||
case EventTypeUnknown:
|
||||
return "unknown"
|
||||
case EventTypeDownloadComplete:
|
||||
return "download complete"
|
||||
case EventTypeInstallComplete:
|
||||
return "install complete"
|
||||
case EventTypeUpdateComplete:
|
||||
return "update complete"
|
||||
case EventTypeUninstall:
|
||||
return "uninstall"
|
||||
case EventTypeDownloadStarted:
|
||||
return "download started"
|
||||
case EventTypeInstallStarted:
|
||||
return "install started"
|
||||
case EventTypeNewApplicationInstallStarted:
|
||||
return "new application install started"
|
||||
case EventTypeSetupStarted:
|
||||
return "setup started"
|
||||
case EventTypeSetupFinished:
|
||||
return "setup finished"
|
||||
case EventTypeUpdateApplicationStarted:
|
||||
return "update application started"
|
||||
case EventTypeUpdateDownloadStarted:
|
||||
return "update download started"
|
||||
case EventTypeUpdateDownloadFinished:
|
||||
return "update download finished"
|
||||
case EventTypeUpdateInstallerStarted:
|
||||
return "update installer started"
|
||||
case EventTypeSetupUpdateBegin:
|
||||
return "setup update begin"
|
||||
case EventTypeSetupUpdateComplete:
|
||||
return "setup update complete"
|
||||
case EventTypeRegisterProductComplete:
|
||||
return "register product complete"
|
||||
case EventTypeOEMInstallFirstCheck:
|
||||
return "OEM install first check"
|
||||
case EventTypeAppSpecificCommandStarted:
|
||||
return "app-specific command started"
|
||||
case EventTypeAppSpecificCommandEnded:
|
||||
return "app-specific command ended"
|
||||
case EventTypeSetupFailure:
|
||||
return "setup failure"
|
||||
case EventTypeComServerFailure:
|
||||
return "COM server failure"
|
||||
case EventTypeSetupUpdateFailure:
|
||||
return "setup update failure "
|
||||
default:
|
||||
return fmt.Sprintf("event %d", e)
|
||||
}
|
||||
}
|
||||
|
||||
type EventResult int
|
||||
|
||||
const (
|
||||
EventResultError EventResult = 0
|
||||
EventResultSuccess EventResult = 1
|
||||
EventResultSuccessReboot EventResult = 2
|
||||
EventResultSuccessRestartBrowser EventResult = 3
|
||||
EventResultCancelled EventResult = 4
|
||||
EventResultErrorInstallerMSI EventResult = 5
|
||||
EventResultErrorInstallerOther EventResult = 6
|
||||
EventResultNoUpdate EventResult = 7
|
||||
EventResultInstallerSystem EventResult = 8
|
||||
EventResultUpdateDeferred EventResult = 9
|
||||
EventResultHandoffError EventResult = 10
|
||||
)
|
||||
|
||||
func (e EventResult) String() string {
|
||||
switch e {
|
||||
case EventResultError:
|
||||
return "error"
|
||||
case EventResultSuccess:
|
||||
return "success"
|
||||
case EventResultSuccessReboot:
|
||||
return "success reboot"
|
||||
case EventResultSuccessRestartBrowser:
|
||||
return "success restart browser"
|
||||
case EventResultCancelled:
|
||||
return "cancelled"
|
||||
case EventResultErrorInstallerMSI:
|
||||
return "error installer MSI"
|
||||
case EventResultErrorInstallerOther:
|
||||
return "error installer other"
|
||||
case EventResultNoUpdate:
|
||||
return "noupdate"
|
||||
case EventResultInstallerSystem:
|
||||
return "error installer system"
|
||||
case EventResultUpdateDeferred:
|
||||
return "update deferred"
|
||||
case EventResultHandoffError:
|
||||
return "handoff error"
|
||||
default:
|
||||
return fmt.Sprintf("result %d", e)
|
||||
}
|
||||
}
|
||||
|
||||
type AppStatus string
|
||||
|
||||
const (
|
||||
// Standard values
|
||||
AppOK AppStatus = "ok"
|
||||
AppRestricted AppStatus = "restricted"
|
||||
AppUnknownID AppStatus = "error-unknownApplication"
|
||||
AppInvalidID AppStatus = "error-invalidAppId"
|
||||
|
||||
// Extra error values
|
||||
AppInvalidVersion AppStatus = "error-invalidVersion"
|
||||
AppInternalError AppStatus = "error-internal"
|
||||
)
|
||||
|
||||
// Make AppStatus easy to use as an error
|
||||
func (a AppStatus) Error() string {
|
||||
return "omaha: app status " + string(a)
|
||||
}
|
||||
|
||||
type UpdateStatus string
|
||||
|
||||
const (
|
||||
NoUpdate UpdateStatus = "noupdate"
|
||||
UpdateOK UpdateStatus = "ok"
|
||||
UpdateOSNotSupported UpdateStatus = "error-osnotsupported"
|
||||
UpdateUnsupportedProtocol UpdateStatus = "error-unsupportedProtocol"
|
||||
UpdatePluginRestrictedHost UpdateStatus = "error-pluginRestrictedHost"
|
||||
UpdateHashError UpdateStatus = "error-hash"
|
||||
UpdateInternalError UpdateStatus = "error-internal"
|
||||
)
|
||||
|
||||
// Make UpdateStatus easy to use as an error
|
||||
func (u UpdateStatus) Error() string {
|
||||
return "omaha: update status " + string(u)
|
||||
}
|
127
omaha/handler.go
127
omaha/handler.go
|
@ -1,127 +0,0 @@
|
|||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package omaha
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type OmahaHandler struct {
|
||||
Updater
|
||||
}
|
||||
|
||||
func (o *OmahaHandler) ServeHTTP(w http.ResponseWriter, httpReq *http.Request) {
|
||||
if httpReq.Method != "POST" {
|
||||
log.Printf("omaha: Unexpected HTTP method: %s", httpReq.Method)
|
||||
http.Error(w, "Expected a POST", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// A request over 1M in size is certainly bogus.
|
||||
reader := http.MaxBytesReader(w, httpReq.Body, 1024*1024)
|
||||
contentType := httpReq.Header.Get("Content-Type")
|
||||
omahaReq, err := ParseRequest(contentType, reader)
|
||||
if err != nil {
|
||||
log.Printf("omaha: Failed parsing request: %v", err)
|
||||
http.Error(w, "Bad Omaha Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
httpStatus := 0
|
||||
omahaResp := NewResponse()
|
||||
for _, appReq := range omahaReq.Apps {
|
||||
appResp := o.serveApp(omahaResp, httpReq, omahaReq, appReq)
|
||||
if appResp.Status == AppOK {
|
||||
// HTTP is ok if any app is ok.
|
||||
httpStatus = http.StatusOK
|
||||
} else if httpStatus == 0 {
|
||||
// If no app is ok HTTP will use the first error.
|
||||
if appResp.Status == AppInternalError {
|
||||
httpStatus = http.StatusInternalServerError
|
||||
} else {
|
||||
httpStatus = http.StatusBadRequest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if httpStatus == 0 {
|
||||
httpStatus = http.StatusBadRequest
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/xml; charset=utf-8")
|
||||
w.WriteHeader(httpStatus)
|
||||
|
||||
if _, err := w.Write([]byte(xml.Header)); err != nil {
|
||||
log.Printf("omaha: Failed writing response: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
encoder := xml.NewEncoder(w)
|
||||
encoder.Indent("", "\t")
|
||||
if err := encoder.Encode(omahaResp); err != nil {
|
||||
log.Printf("omaha: Failed encoding response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *OmahaHandler) serveApp(omahaResp *Response, httpReq *http.Request, omahaReq *Request, appReq *AppRequest) *AppResponse {
|
||||
if err := o.CheckApp(omahaReq, appReq); err != nil {
|
||||
if appStatus, ok := err.(AppStatus); ok {
|
||||
return omahaResp.AddApp(appReq.ID, appStatus)
|
||||
}
|
||||
log.Printf("omaha: CheckApp failed: %v", err)
|
||||
return omahaResp.AddApp(appReq.ID, AppInternalError)
|
||||
}
|
||||
|
||||
appResp := omahaResp.AddApp(appReq.ID, AppOK)
|
||||
if appReq.UpdateCheck != nil {
|
||||
o.checkUpdate(appResp, httpReq, omahaReq, appReq)
|
||||
}
|
||||
|
||||
if appReq.Ping != nil {
|
||||
o.Ping(omahaReq, appReq)
|
||||
appResp.AddPing()
|
||||
}
|
||||
|
||||
for _, event := range appReq.Events {
|
||||
o.Event(omahaReq, appReq, event)
|
||||
appResp.AddEvent()
|
||||
}
|
||||
|
||||
return appResp
|
||||
}
|
||||
|
||||
func (o *OmahaHandler) checkUpdate(appResp *AppResponse, httpReq *http.Request, omahaReq *Request, appReq *AppRequest) {
|
||||
update, err := o.CheckUpdate(omahaReq, appReq)
|
||||
if err != nil {
|
||||
if updateStatus, ok := err.(UpdateStatus); ok {
|
||||
appResp.AddUpdateCheck(updateStatus)
|
||||
} else {
|
||||
log.Printf("omaha: CheckUpdate failed: %v", err)
|
||||
appResp.AddUpdateCheck(UpdateInternalError)
|
||||
}
|
||||
} else if update != nil {
|
||||
u := appResp.AddUpdateCheck(UpdateOK)
|
||||
fillUpdate(u, update, httpReq)
|
||||
} else {
|
||||
appResp.AddUpdateCheck(NoUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
func fillUpdate(u *UpdateResponse, update *Update, httpReq *http.Request) {
|
||||
u.URLs = update.URLs([]string{"http://" + httpReq.Host})
|
||||
u.Manifest = &update.Manifest
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
// Copyright 2013-2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package omaha
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/kylelemons/godebug/diff"
|
||||
)
|
||||
|
||||
const (
|
||||
testAppID = "{27BD862E-8AE8-4886-A055-F7F1A6460627}"
|
||||
testAppVer = "1.0.0"
|
||||
)
|
||||
|
||||
var (
|
||||
nilRequest *Request
|
||||
nilResponse *Response
|
||||
)
|
||||
|
||||
func init() {
|
||||
nilRequest = NewRequest()
|
||||
nilRequest.AddApp(testAppID, testAppVer)
|
||||
nilResponse = NewResponse()
|
||||
nilResponse.AddApp(testAppID, AppOK)
|
||||
}
|
||||
|
||||
func compareXML(a, b interface{}) error {
|
||||
aXml, err := xml.MarshalIndent(a, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bXml, err := xml.MarshalIndent(b, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d := diff.Diff(string(aXml), string(bXml)); d != "" {
|
||||
err := fmt.Errorf("Unexpected XML:\n%s", d)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestHandleNilRequest(t *testing.T) {
|
||||
handler := OmahaHandler{UpdaterStub{}}
|
||||
response := NewResponse()
|
||||
handler.serveApp(response, nil, nilRequest, nilRequest.Apps[0])
|
||||
if err := compareXML(nilResponse, response); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package omaha
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
// isClosed detects if an error is due to a closed network connection,
|
||||
// working around bug https://github.com/golang/go/issues/4373
|
||||
func isClosed(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
if operr, ok := err.(*net.OpError); ok {
|
||||
err = operr.Err
|
||||
}
|
||||
// cry softly
|
||||
return err.Error() == "use of closed network connection"
|
||||
}
|
248
omaha/omaha.go
Normal file
248
omaha/omaha.go
Normal file
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
Implements the Google omaha protocol.
|
||||
|
||||
Omaha is a request/response protocol using XML. Requests are made by
|
||||
clients and responses are given by the Omaha server.
|
||||
http://code.google.com/p/omaha/wiki/ServerProtocol
|
||||
*/
|
||||
package omaha
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
XMLName xml.Name `xml:"request"`
|
||||
Os Os `xml:"os"`
|
||||
Apps []*App `xml:"app"`
|
||||
Protocol string `xml:"protocol,attr"`
|
||||
Version string `xml:"version,attr,omitempty"`
|
||||
IsMachine string `xml:"ismachine,attr,omitempty"`
|
||||
SessionId string `xml:"sessionid,attr,omitempty"`
|
||||
UserId string `xml:"userid,attr,omitempty"`
|
||||
InstallSource string `xml:"installsource,attr,omitempty"`
|
||||
TestSource string `xml:"testsource,attr,omitempty"`
|
||||
RequestId string `xml:"requestid,attr,omitempty"`
|
||||
UpdaterVersion string `xml:"updaterversion,attr,omitempty"`
|
||||
}
|
||||
|
||||
func NewRequest(version string, platform string, sp string, arch string) *Request {
|
||||
r := new(Request)
|
||||
r.Protocol = "3.0"
|
||||
r.Os = Os{Version: version, Platform: platform, Sp: sp, Arch: arch}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Request) AddApp(id string, version string) *App {
|
||||
a := NewApp(id)
|
||||
a.Version = version
|
||||
r.Apps = append(r.Apps, a)
|
||||
return a
|
||||
}
|
||||
|
||||
/* Response
|
||||
*/
|
||||
type Response struct {
|
||||
XMLName xml.Name `xml:"response"`
|
||||
DayStart DayStart `xml:"daystart"`
|
||||
Apps []*App `xml:"app"`
|
||||
Protocol string `xml:"protocol,attr"`
|
||||
Server string `xml:"server,attr"`
|
||||
}
|
||||
|
||||
func NewResponse(server string) *Response {
|
||||
r := &Response{Server: server, Protocol: "3.0"}
|
||||
r.DayStart.ElapsedSeconds = "0"
|
||||
return r
|
||||
}
|
||||
|
||||
type DayStart struct {
|
||||
ElapsedSeconds string `xml:"elapsed_seconds,attr"`
|
||||
}
|
||||
|
||||
func (r *Response) AddApp(id string) *App {
|
||||
a := NewApp(id)
|
||||
r.Apps = append(r.Apps, a)
|
||||
return a
|
||||
}
|
||||
|
||||
type App struct {
|
||||
XMLName xml.Name `xml:"app"`
|
||||
Ping *Ping `xml:"ping"`
|
||||
UpdateCheck *UpdateCheck `xml:"updatecheck"`
|
||||
Events []*Event `xml:"event"`
|
||||
Id string `xml:"appid,attr,omitempty"`
|
||||
Version string `xml:"version,attr,omitempty"`
|
||||
NextVersion string `xml:"nextversion,attr,omitempty"`
|
||||
Lang string `xml:"lang,attr,omitempty"`
|
||||
Client string `xml:"client,attr,omitempty"`
|
||||
InstallAge string `xml:"installage,attr,omitempty"`
|
||||
FromTrack string `xml:"from_track,attr,omitempty"`
|
||||
Status string `xml:"status,attr,omitempty"`
|
||||
}
|
||||
|
||||
func NewApp(id string) *App {
|
||||
a := &App{Id: id}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *App) AddUpdateCheck() *UpdateCheck {
|
||||
a.UpdateCheck = new(UpdateCheck)
|
||||
return a.UpdateCheck
|
||||
}
|
||||
|
||||
func (a *App) AddPing() *Ping {
|
||||
a.Ping = new(Ping)
|
||||
return a.Ping
|
||||
}
|
||||
|
||||
func (a *App) AddEvent() *Event {
|
||||
event := new(Event)
|
||||
a.Events = append(a.Events, event)
|
||||
return event
|
||||
}
|
||||
|
||||
type UpdateCheck struct {
|
||||
XMLName xml.Name `xml:"updatecheck"`
|
||||
Urls *Urls `xml:"urls"`
|
||||
Manifest *Manifest `xml:"manifest"`
|
||||
TargetVersionPrefix string `xml:"targetversionprefix,attr,omitempty"`
|
||||
Status string `xml:"status,attr,omitempty"`
|
||||
}
|
||||
|
||||
func (u *UpdateCheck) AddUrl(codebase string) *Url {
|
||||
if u.Urls == nil {
|
||||
u.Urls = new(Urls)
|
||||
}
|
||||
url := new(Url)
|
||||
url.CodeBase = codebase
|
||||
u.Urls.Urls = append(u.Urls.Urls, *url)
|
||||
return url
|
||||
}
|
||||
|
||||
func (u *UpdateCheck) AddManifest(version string) *Manifest {
|
||||
u.Manifest = &Manifest{Version: version}
|
||||
return u.Manifest
|
||||
}
|
||||
|
||||
type Ping struct {
|
||||
XMLName xml.Name `xml:"ping"`
|
||||
LastReportDays string `xml:"r,attr,omitempty"`
|
||||
Status string `xml:"status,attr,omitempty"`
|
||||
}
|
||||
|
||||
type Os struct {
|
||||
XMLName xml.Name `xml:"os"`
|
||||
Platform string `xml:"platform,attr,omitempty"`
|
||||
Version string `xml:"version,attr,omitempty"`
|
||||
Sp string `xml:"sp,attr,omitempty"`
|
||||
Arch string `xml:"arch,attr,omitempty"`
|
||||
}
|
||||
|
||||
func NewOs(platform string, version string, sp string, arch string) *Os {
|
||||
o := &Os{Version: version, Platform: platform, Sp: sp, Arch: arch}
|
||||
return o
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
XMLName xml.Name `xml:"event"`
|
||||
Type string `xml:"eventtype,attr,omitempty"`
|
||||
Result string `xml:"eventresult,attr,omitempty"`
|
||||
PreviousVersion string `xml:"previousversion,attr,omitempty"`
|
||||
}
|
||||
|
||||
type Urls struct {
|
||||
XMLName xml.Name `xml:"urls"`
|
||||
Urls []Url `xml:"url"`
|
||||
}
|
||||
|
||||
type Url struct {
|
||||
XMLName xml.Name `xml:"url"`
|
||||
CodeBase string `xml:"codebase,attr"`
|
||||
}
|
||||
|
||||
type Manifest struct {
|
||||
XMLName xml.Name `xml:"manifest"`
|
||||
Packages Packages `xml:"packages"`
|
||||
Actions Actions `xml:"actions"`
|
||||
Version string `xml:"version,attr"`
|
||||
}
|
||||
|
||||
type Packages struct {
|
||||
XMLName xml.Name `xml:"packages"`
|
||||
Packages []Package `xml:"package"`
|
||||
}
|
||||
|
||||
type Package struct {
|
||||
XMLName xml.Name `xml:"package"`
|
||||
Hash string `xml:"hash,attr"`
|
||||
Name string `xml:"name,attr"`
|
||||
Size string `xml:"size,attr"`
|
||||
Required bool `xml:"required,attr"`
|
||||
}
|
||||
|
||||
func (m *Manifest) AddPackage(hash string, name string, size string, required bool) *Package {
|
||||
p := &Package{Hash: hash, Name: name, Size: size, Required: required}
|
||||
m.Packages.Packages = append(m.Packages.Packages, *p)
|
||||
return p
|
||||
}
|
||||
|
||||
type Actions struct {
|
||||
XMLName xml.Name `xml:"actions"`
|
||||
Actions []*Action `xml:"action"`
|
||||
}
|
||||
|
||||
type Action struct {
|
||||
XMLName xml.Name `xml:"action"`
|
||||
Event string `xml:"event,attr"`
|
||||
ChromeOSVersion string `xml:"ChromeOSVersion,attr"`
|
||||
Sha256 string `xml:"sha256,attr"`
|
||||
NeedsAdmin bool `xml:"needsadmin,attr"`
|
||||
IsDelta bool `xml:"IsDelta,attr"`
|
||||
}
|
||||
|
||||
func (m *Manifest) AddAction(event string) *Action {
|
||||
a := &Action{Event: event}
|
||||
m.Actions.Actions = append(m.Actions.Actions, a)
|
||||
return a
|
||||
}
|
||||
|
||||
var EventTypes = map[int]string{
|
||||
0: "unknown",
|
||||
1: "download complete",
|
||||
2: "install complete",
|
||||
3: "update complete",
|
||||
4: "uninstall",
|
||||
5: "download started",
|
||||
6: "install started",
|
||||
9: "new application install started",
|
||||
10: "setup started",
|
||||
11: "setup finished",
|
||||
12: "update application started",
|
||||
13: "update download started",
|
||||
14: "update download finished",
|
||||
15: "update installer started",
|
||||
16: "setup update begin",
|
||||
17: "setup update complete",
|
||||
20: "register product complete",
|
||||
30: "OEM install first check",
|
||||
40: "app-specific command started",
|
||||
41: "app-specific command ended",
|
||||
100: "setup failure",
|
||||
102: "COM server failure",
|
||||
103: "setup update failure",
|
||||
}
|
||||
|
||||
var EventResults = map[int]string{
|
||||
0: "error",
|
||||
1: "success",
|
||||
2: "success reboot",
|
||||
3: "success restart browser",
|
||||
4: "cancelled",
|
||||
5: "error installer MSI",
|
||||
6: "error installer other",
|
||||
7: "noupdate",
|
||||
8: "error installer system",
|
||||
9: "update deferred",
|
||||
10: "handoff error",
|
||||
}
|
120
omaha/omaha_test.go
Normal file
120
omaha/omaha_test.go
Normal file
|
@ -0,0 +1,120 @@
|
|||
package omaha
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOmahaRequestUpdateCheck(t *testing.T) {
|
||||
file, err := os.Open("../fixtures/update-engine/update/request.xml")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
fix, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
v := Request{}
|
||||
xml.Unmarshal(fix, &v)
|
||||
|
||||
if v.Os.Version != "Indy" {
|
||||
t.Error("Unexpected version", v.Os.Version)
|
||||
}
|
||||
|
||||
if v.Apps[0].Id != "{87efface-864d-49a5-9bb3-4b050a7c227a}" {
|
||||
t.Error("Expected an App Id")
|
||||
}
|
||||
|
||||
if v.Apps[0].UpdateCheck == nil {
|
||||
t.Error("Expected an UpdateCheck")
|
||||
}
|
||||
|
||||
if v.Apps[0].Version != "ForcedUpdate" {
|
||||
t.Error("Verison is ForcedUpdate")
|
||||
}
|
||||
|
||||
if v.Apps[0].FromTrack != "developer-build" {
|
||||
t.Error("developer-build")
|
||||
}
|
||||
|
||||
if v.Apps[0].Events[0].Type != "3" {
|
||||
t.Error("developer-build")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleOmaha_NewResponse() {
|
||||
response := NewResponse("unit-test")
|
||||
app := response.AddApp("{52F1B9BC-D31A-4D86-9276-CBC256AADF9A}")
|
||||
app.Status = "ok"
|
||||
p := app.AddPing()
|
||||
p.Status = "ok"
|
||||
u := app.AddUpdateCheck()
|
||||
u.Status = "ok"
|
||||
u.AddUrl("http://localhost/updates")
|
||||
m := u.AddManifest("9999.0.0")
|
||||
m.AddPackage("+LXvjiaPkeYDLHoNKlf9qbJwvnk=", "update.gz", "67546213", true)
|
||||
a := m.AddAction("postinstall")
|
||||
a.ChromeOSVersion = "9999.0.0"
|
||||
a.Sha256 = "0VAlQW3RE99SGtSB5R4m08antAHO8XDoBMKDyxQT/Mg="
|
||||
a.NeedsAdmin = false
|
||||
a.IsDelta = true
|
||||
|
||||
if raw, err := xml.MarshalIndent(response, "", " "); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
} else {
|
||||
fmt.Printf("%s%s\n", xml.Header, raw)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// <?xml version="1.0" encoding="UTF-8"?>
|
||||
// <response protocol="3.0" server="unit-test">
|
||||
// <daystart elapsed_seconds="0"></daystart>
|
||||
// <app appid="{52F1B9BC-D31A-4D86-9276-CBC256AADF9A}" status="ok">
|
||||
// <ping status="ok"></ping>
|
||||
// <updatecheck status="ok">
|
||||
// <urls>
|
||||
// <url codebase="http://localhost/updates"></url>
|
||||
// </urls>
|
||||
// <manifest version="9999.0.0">
|
||||
// <packages>
|
||||
// <package hash="+LXvjiaPkeYDLHoNKlf9qbJwvnk=" name="update.gz" size="67546213" required="true"></package>
|
||||
// </packages>
|
||||
// <actions>
|
||||
// <action event="postinstall" ChromeOSVersion="9999.0.0" sha256="0VAlQW3RE99SGtSB5R4m08antAHO8XDoBMKDyxQT/Mg=" needsadmin="false" IsDelta="true"></action>
|
||||
// </actions>
|
||||
// </manifest>
|
||||
// </updatecheck>
|
||||
// </app>
|
||||
// </response>
|
||||
}
|
||||
|
||||
func ExampleOmaha_NewRequest() {
|
||||
request := NewRequest("Indy", "Chrome OS", "ForcedUpdate_x86_64", "")
|
||||
app := request.AddApp("{27BD862E-8AE8-4886-A055-F7F1A6460627}", "1.0.0.0")
|
||||
app.AddUpdateCheck()
|
||||
|
||||
event := app.AddEvent()
|
||||
event.Type = "1"
|
||||
event.Result = "0"
|
||||
|
||||
if raw, err := xml.MarshalIndent(request, "", " "); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
} else {
|
||||
fmt.Printf("%s%s\n", xml.Header, raw)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// <?xml version="1.0" encoding="UTF-8"?>
|
||||
// <request protocol="3.0">
|
||||
// <os platform="Chrome OS" version="Indy" sp="ForcedUpdate_x86_64"></os>
|
||||
// <app appid="{27BD862E-8AE8-4886-A055-F7F1A6460627}" version="1.0.0.0">
|
||||
// <updatecheck></updatecheck>
|
||||
// <event eventtype="1" eventresult="0"></event>
|
||||
// </app>
|
||||
// </request>
|
||||
}
|
111
omaha/package.go
111
omaha/package.go
|
@ -1,111 +0,0 @@
|
|||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package omaha
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var (
|
||||
PackageHashMismatchError = errors.New("package hash is invalid")
|
||||
PackageSizeMismatchError = errors.New("package size is invalid")
|
||||
)
|
||||
|
||||
// Package represents a single downloadable file.
|
||||
type Package struct {
|
||||
Name string `xml:"name,attr"`
|
||||
SHA1 string `xml:"hash,attr"`
|
||||
SHA256 string `xml:"hash_sha256,attr,omitempty"`
|
||||
Size uint64 `xml:"size,attr"`
|
||||
Required bool `xml:"required,attr"`
|
||||
}
|
||||
|
||||
func (p *Package) FromPath(name string) error {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
err = p.FromReader(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.Name = filepath.Base(name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Package) FromReader(r io.Reader) error {
|
||||
sha1b64, sha256b64, n, err := multihash(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.SHA1 = sha1b64
|
||||
p.SHA256 = sha256b64
|
||||
p.Size = uint64(n)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Package) Verify(dir string) error {
|
||||
f, err := os.Open(filepath.Join(dir, p.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return p.VerifyReader(f)
|
||||
}
|
||||
|
||||
func (p *Package) VerifyReader(r io.Reader) error {
|
||||
sha1b64, sha256b64, n, err := multihash(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.Size != uint64(n) {
|
||||
return PackageSizeMismatchError
|
||||
}
|
||||
|
||||
if p.SHA1 != sha1b64 {
|
||||
return PackageHashMismatchError
|
||||
}
|
||||
|
||||
// Allow SHA256 to be empty since it is a later protocol addition.
|
||||
if p.SHA256 != "" && p.SHA256 != sha256b64 {
|
||||
return PackageHashMismatchError
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func multihash(r io.Reader) (sha1b64, sha256b64 string, n int64, err error) {
|
||||
h1 := sha1.New()
|
||||
h256 := sha256.New()
|
||||
if n, err = io.Copy(io.MultiWriter(h1, h256), r); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sha1b64 = base64.StdEncoding.EncodeToString(h1.Sum(nil))
|
||||
sha256b64 = base64.StdEncoding.EncodeToString(h256.Sum(nil))
|
||||
return
|
||||
}
|
|
@ -1,144 +0,0 @@
|
|||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package omaha
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
)
|
||||
|
||||
func TestPackageFromPath(t *testing.T) {
|
||||
expect := Package{
|
||||
Name: "null",
|
||||
SHA1: "2jmj7l5rSw0yVb/vlWAYkK/YBwk=",
|
||||
SHA256: "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
|
||||
Size: 0,
|
||||
Required: false,
|
||||
}
|
||||
|
||||
p := Package{}
|
||||
if err := p.FromPath("/dev/null"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if diff := pretty.Compare(expect, p); diff != "" {
|
||||
t.Errorf("Hashing /dev/null failed: %v", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProtocolFromReader(t *testing.T) {
|
||||
data := strings.NewReader("testing\n")
|
||||
expect := Package{
|
||||
Name: "",
|
||||
SHA1: "mAFznarkTsUpPU4fU9P00tQm2Rw=",
|
||||
SHA256: "EqYfThc/s6EcBdZHH3Ryj3YjG0pfzZZnzvOvh6OuTcI=",
|
||||
Size: 8,
|
||||
Required: false,
|
||||
}
|
||||
|
||||
p := Package{}
|
||||
if err := p.FromReader(data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if diff := pretty.Compare(expect, p); diff != "" {
|
||||
t.Errorf("Hashing failed: %v", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackageVerify(t *testing.T) {
|
||||
p := Package{
|
||||
Name: "null",
|
||||
SHA1: "2jmj7l5rSw0yVb/vlWAYkK/YBwk=",
|
||||
SHA256: "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
|
||||
Size: 0,
|
||||
Required: false,
|
||||
}
|
||||
|
||||
if err := p.Verify("/dev"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackageVerifyNoSHA256(t *testing.T) {
|
||||
p := Package{
|
||||
Name: "null",
|
||||
SHA1: "2jmj7l5rSw0yVb/vlWAYkK/YBwk=",
|
||||
SHA256: "",
|
||||
Size: 0,
|
||||
Required: false,
|
||||
}
|
||||
|
||||
if err := p.Verify("/dev"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackageVerifyBadSize(t *testing.T) {
|
||||
p := Package{
|
||||
Name: "null",
|
||||
SHA1: "2jmj7l5rSw0yVb/vlWAYkK/YBwk=",
|
||||
SHA256: "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
|
||||
Size: 1,
|
||||
Required: false,
|
||||
}
|
||||
|
||||
err := p.Verify("/dev")
|
||||
if err == nil {
|
||||
t.Error("verify passed")
|
||||
}
|
||||
if err != PackageSizeMismatchError {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPackageVerifyBadSHA1(t *testing.T) {
|
||||
p := Package{
|
||||
Name: "null",
|
||||
SHA1: "xxxxxxxxxxxxxxxxxxxxxxxxxxx=",
|
||||
SHA256: "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
|
||||
Size: 0,
|
||||
Required: false,
|
||||
}
|
||||
|
||||
err := p.Verify("/dev")
|
||||
if err == nil {
|
||||
t.Error("verify passed")
|
||||
}
|
||||
if err != PackageHashMismatchError {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackageVerifyBadSHA256(t *testing.T) {
|
||||
p := Package{
|
||||
Name: "null",
|
||||
SHA1: "2jmj7l5rSw0yVb/vlWAYkK/YBwk=",
|
||||
SHA256: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=",
|
||||
Size: 0,
|
||||
Required: false,
|
||||
}
|
||||
|
||||
err := p.Verify("/dev")
|
||||
if err == nil {
|
||||
t.Error("verify passed")
|
||||
}
|
||||
if err != PackageHashMismatchError {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
// Copyright 2017 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package omaha
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// checkContentType verifies the HTTP Content-Type header properly
|
||||
// declares the document is XML and UTF-8. Blank is assumed OK.
|
||||
func checkContentType(contentType string) error {
|
||||
if contentType == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
mType, mParams, err := mime.ParseMediaType(contentType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if mType != "text/xml" && mType != "application/xml" {
|
||||
return fmt.Errorf("unsupported content type %q", mType)
|
||||
}
|
||||
|
||||
charset, _ := mParams["charset"]
|
||||
if charset != "" && strings.ToLower(charset) != "utf-8" {
|
||||
return fmt.Errorf("unsupported content charset %q", charset)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseReqOrResp parses Request and Response objects.
|
||||
func parseReqOrResp(r io.Reader, v interface{}) error {
|
||||
decoder := xml.NewDecoder(r)
|
||||
if err := decoder.Decode(v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var protocol string
|
||||
switch v := v.(type) {
|
||||
case *Request:
|
||||
protocol = v.Protocol
|
||||
case *Response:
|
||||
protocol = v.Protocol
|
||||
default:
|
||||
panic(fmt.Errorf("unexpected type %T", v))
|
||||
}
|
||||
|
||||
if protocol != "3.0" {
|
||||
return fmt.Errorf("unsupported omaha protocol: %q", protocol)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
// Copyright 2017 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package omaha
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheckContentType(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
ct string
|
||||
ok bool
|
||||
}{
|
||||
{"", true},
|
||||
{"text/xml", true},
|
||||
{"text/XML", true},
|
||||
{"application/xml", true},
|
||||
{"text/plain", false},
|
||||
{"xml", false},
|
||||
{"text/xml; charset=utf-8", true},
|
||||
{"text/xml; charset=UTF-8", true},
|
||||
{"text/xml; charset=ascii", false},
|
||||
} {
|
||||
err := checkContentType(tt.ct)
|
||||
if tt.ok && err != nil {
|
||||
t.Errorf("%q failed: %v", tt.ct, err)
|
||||
}
|
||||
if !tt.ok && err == nil {
|
||||
t.Errorf("%q was not rejected", tt.ct)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBadVersion(t *testing.T) {
|
||||
r := strings.NewReader(`<request protocol="2.0"></request>`)
|
||||
err := parseReqOrResp(r, &Request{})
|
||||
if err == nil {
|
||||
t.Error("Bad protocol version was accepted")
|
||||
} else if err.Error() != `unsupported omaha protocol: "2.0"` {
|
||||
t.Errorf("Wrong error: %v", err)
|
||||
}
|
||||
}
|
|
@ -1,304 +0,0 @@
|
|||
// Copyright 2013-2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Google's Omaha application update protocol, version 3.
|
||||
//
|
||||
// Omaha is a poll based protocol using XML. Requests are made by clients to
|
||||
// check for updates or report events of an update process. Responses are given
|
||||
// by the server to provide update information, if any, or to simply
|
||||
// acknowledge the receipt of event status.
|
||||
//
|
||||
// https://github.com/google/omaha/blob/master/doc/ServerProtocolV3.md
|
||||
package omaha
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Request sent by the Omaha client
|
||||
type Request struct {
|
||||
XMLName xml.Name `xml:"request" json:"-"`
|
||||
OS *OS `xml:"os"`
|
||||
Apps []*AppRequest `xml:"app"`
|
||||
Protocol string `xml:"protocol,attr"`
|
||||
InstallSource string `xml:"installsource,attr,omitempty"`
|
||||
IsMachine int `xml:"ismachine,attr,omitempty"`
|
||||
RequestID string `xml:"requestid,attr,omitempty"`
|
||||
SessionID string `xml:"sessionid,attr,omitempty"`
|
||||
TestSource string `xml:"testsource,attr,omitempty"`
|
||||
UserID string `xml:"userid,attr,omitempty"`
|
||||
Version string `xml:"version,attr,omitempty"`
|
||||
|
||||
// update engine extension, duplicates the version attribute.
|
||||
UpdaterVersion string `xml:"updaterversion,attr,omitempty"`
|
||||
}
|
||||
|
||||
func NewRequest() *Request {
|
||||
return &Request{
|
||||
Protocol: "3.0",
|
||||
// TODO(marineam) set a default client Version
|
||||
OS: &OS{
|
||||
Platform: LocalPlatform(),
|
||||
Arch: LocalArch(),
|
||||
// TODO(marineam): Version and ServicePack
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ParseRequest verifies and returns the parsed Request document.
|
||||
// The MIME Content-Type header may be provided to sanity check its
|
||||
// value; if blank it is assumed to be XML in UTF-8.
|
||||
func ParseRequest(contentType string, body io.Reader) (*Request, error) {
|
||||
if err := checkContentType(contentType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := &Request{}
|
||||
if err := parseReqOrResp(body, r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *Request) AddApp(id, version string) *AppRequest {
|
||||
a := &AppRequest{ID: id, Version: version}
|
||||
r.Apps = append(r.Apps, a)
|
||||
return a
|
||||
}
|
||||
|
||||
func (r *Request) GetApp(id string) *AppRequest {
|
||||
for _, app := range r.Apps {
|
||||
if app.ID == id {
|
||||
return app
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AppRequest struct {
|
||||
Ping *PingRequest `xml:"ping"`
|
||||
UpdateCheck *UpdateRequest `xml:"updatecheck"`
|
||||
Events []*EventRequest `xml:"event" json:",omitempty"`
|
||||
ID string `xml:"appid,attr,omitempty"`
|
||||
Client string `xml:"client,attr,omitempty"`
|
||||
InstallAge string `xml:"installage,attr,omitempty"`
|
||||
Lang string `xml:"lang,attr,omitempty"`
|
||||
NextVersion string `xml:"nextversion,attr,omitempty"`
|
||||
Version string `xml:"version,attr,omitempty"`
|
||||
|
||||
// update engine extensions
|
||||
Board string `xml:"board,attr,omitempty"`
|
||||
DeltaOK bool `xml:"delta_okay,attr,omitempty"`
|
||||
FromTrack string `xml:"from_track,attr,omitempty"`
|
||||
Track string `xml:"track,attr,omitempty"`
|
||||
|
||||
// coreos update engine extensions
|
||||
AlephVersion string `xml:"alephversion,attr,omitempty"`
|
||||
BootID string `xml:"bootid,attr,omitempty"`
|
||||
MachineID string `xml:"machineid,attr,omitempty"`
|
||||
OEM string `xml:"oem,attr,omitempty"`
|
||||
OEMVersion string `xml:"oemversion,attr,omitempty"`
|
||||
}
|
||||
|
||||
func (a *AppRequest) AddUpdateCheck() *UpdateRequest {
|
||||
a.UpdateCheck = &UpdateRequest{}
|
||||
return a.UpdateCheck
|
||||
}
|
||||
|
||||
func (a *AppRequest) AddPing() *PingRequest {
|
||||
a.Ping = &PingRequest{Active: 1}
|
||||
return a.Ping
|
||||
}
|
||||
|
||||
func (a *AppRequest) AddEvent() *EventRequest {
|
||||
event := &EventRequest{}
|
||||
a.Events = append(a.Events, event)
|
||||
return event
|
||||
}
|
||||
|
||||
type UpdateRequest struct {
|
||||
TargetVersionPrefix string `xml:"targetversionprefix,attr,omitempty"`
|
||||
}
|
||||
|
||||
type PingRequest struct {
|
||||
Active int `xml:"active,attr,omitempty"`
|
||||
LastActiveReportDays *int `xml:"a,attr,omitempty"`
|
||||
LastReportDays int `xml:"r,attr,omitempty"`
|
||||
}
|
||||
|
||||
type EventRequest struct {
|
||||
Type EventType `xml:"eventtype,attr"`
|
||||
Result EventResult `xml:"eventresult,attr"`
|
||||
ErrorCode int `xml:"errorcode,attr,omitempty"`
|
||||
NextVersion string `xml:"nextversion,attr,omitempty"`
|
||||
PreviousVersion string `xml:"previousversion,attr,omitempty"`
|
||||
}
|
||||
|
||||
// Response sent by the Omaha server
|
||||
type Response struct {
|
||||
XMLName xml.Name `xml:"response" json:"-"`
|
||||
DayStart DayStart `xml:"daystart"`
|
||||
Apps []*AppResponse `xml:"app"`
|
||||
Protocol string `xml:"protocol,attr"`
|
||||
Server string `xml:"server,attr"`
|
||||
}
|
||||
|
||||
func NewResponse() *Response {
|
||||
return &Response{
|
||||
Protocol: "3.0",
|
||||
Server: "go-omaha",
|
||||
DayStart: DayStart{ElapsedSeconds: "0"},
|
||||
}
|
||||
}
|
||||
|
||||
// ParseResponse verifies and returns the parsed Response document.
|
||||
// The MIME Content-Type header may be provided to sanity check its
|
||||
// value; if blank it is assumed to be XML in UTF-8.
|
||||
func ParseResponse(contentType string, body io.Reader) (*Response, error) {
|
||||
if err := checkContentType(contentType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := &Response{}
|
||||
if err := parseReqOrResp(body, r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
type DayStart struct {
|
||||
ElapsedSeconds string `xml:"elapsed_seconds,attr"`
|
||||
}
|
||||
|
||||
func (r *Response) AddApp(id string, status AppStatus) *AppResponse {
|
||||
a := &AppResponse{ID: id, Status: status}
|
||||
r.Apps = append(r.Apps, a)
|
||||
return a
|
||||
}
|
||||
|
||||
func (r *Response) GetApp(id string) *AppResponse {
|
||||
for _, app := range r.Apps {
|
||||
if app.ID == id {
|
||||
return app
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AppResponse struct {
|
||||
Ping *PingResponse `xml:"ping"`
|
||||
UpdateCheck *UpdateResponse `xml:"updatecheck"`
|
||||
Events []*EventResponse `xml:"event" json:",omitempty"`
|
||||
ID string `xml:"appid,attr,omitempty"`
|
||||
Status AppStatus `xml:"status,attr,omitempty"`
|
||||
}
|
||||
|
||||
func (a *AppResponse) AddUpdateCheck(status UpdateStatus) *UpdateResponse {
|
||||
a.UpdateCheck = &UpdateResponse{Status: status}
|
||||
return a.UpdateCheck
|
||||
}
|
||||
|
||||
func (a *AppResponse) AddPing() *PingResponse {
|
||||
a.Ping = &PingResponse{"ok"}
|
||||
return a.Ping
|
||||
}
|
||||
|
||||
func (a *AppResponse) AddEvent() *EventResponse {
|
||||
event := &EventResponse{"ok"}
|
||||
a.Events = append(a.Events, event)
|
||||
return event
|
||||
}
|
||||
|
||||
type UpdateResponse struct {
|
||||
URLs []*URL `xml:"urls>url" json:",omitempty"`
|
||||
Manifest *Manifest `xml:"manifest"`
|
||||
Status UpdateStatus `xml:"status,attr,omitempty"`
|
||||
}
|
||||
|
||||
func (u *UpdateResponse) AddURL(codebase string) *URL {
|
||||
url := &URL{CodeBase: codebase}
|
||||
u.URLs = append(u.URLs, url)
|
||||
return url
|
||||
}
|
||||
|
||||
func (u *UpdateResponse) AddManifest(version string) *Manifest {
|
||||
u.Manifest = &Manifest{Version: version}
|
||||
return u.Manifest
|
||||
}
|
||||
|
||||
type PingResponse struct {
|
||||
Status string `xml:"status,attr"` // Always "ok".
|
||||
}
|
||||
|
||||
type EventResponse struct {
|
||||
Status string `xml:"status,attr"` // Always "ok".
|
||||
}
|
||||
|
||||
type OS struct {
|
||||
Platform string `xml:"platform,attr,omitempty"`
|
||||
Version string `xml:"version,attr,omitempty"`
|
||||
ServicePack string `xml:"sp,attr,omitempty"`
|
||||
Arch string `xml:"arch,attr,omitempty"`
|
||||
}
|
||||
|
||||
type URL struct {
|
||||
CodeBase string `xml:"codebase,attr"`
|
||||
}
|
||||
|
||||
type Manifest struct {
|
||||
Packages []*Package `xml:"packages>package"`
|
||||
Actions []*Action `xml:"actions>action"`
|
||||
Version string `xml:"version,attr"`
|
||||
}
|
||||
|
||||
func (m *Manifest) AddPackage() *Package {
|
||||
p := &Package{}
|
||||
m.Packages = append(m.Packages, p)
|
||||
return p
|
||||
}
|
||||
|
||||
func (m *Manifest) AddPackageFromPath(path string) (*Package, error) {
|
||||
p := &Package{}
|
||||
if err := p.FromPath(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.Packages = append(m.Packages, p)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (m *Manifest) AddAction(event string) *Action {
|
||||
a := &Action{Event: event}
|
||||
m.Actions = append(m.Actions, a)
|
||||
return a
|
||||
}
|
||||
|
||||
type Action struct {
|
||||
Event string `xml:"event,attr"`
|
||||
|
||||
// update engine extensions for event="postinstall"
|
||||
DisplayVersion string `xml:"DisplayVersion,attr,omitempty"`
|
||||
SHA256 string `xml:"sha256,attr,omitempty"`
|
||||
NeedsAdmin bool `xml:"needsadmin,attr,omitempty"`
|
||||
IsDeltaPayload bool `xml:"IsDeltaPayload,attr,omitempty"`
|
||||
DisablePayloadBackoff bool `xml:"DisablePayloadBackoff,attr,omitempty"`
|
||||
MaxFailureCountPerURL uint `xml:"MaxFailureCountPerUrl,attr,omitempty"`
|
||||
MetadataSignatureRsa string `xml:"MetadataSignatureRsa,attr,omitempty"`
|
||||
MetadataSize string `xml:"MetadataSize,attr,omitempty"`
|
||||
Deadline string `xml:"deadline,attr,omitempty"`
|
||||
MoreInfo string `xml:"MoreInfo,attr,omitempty"`
|
||||
Prompt bool `xml:"Prompt,attr,omitempty"`
|
||||
}
|
|
@ -1,247 +0,0 @@
|
|||
// Copyright 2013-2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package omaha
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
sampleRequest = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<request protocol="3.0" version="ChromeOSUpdateEngine-0.1.0.0" updaterversion="ChromeOSUpdateEngine-0.1.0.0" installsource="ondemandupdate" ismachine="1">
|
||||
<os version="Indy" platform="Chrome OS" sp="ForcedUpdate_x86_64"></os>
|
||||
<app appid="{87efface-864d-49a5-9bb3-4b050a7c227a}" bootid="{7D52A1CC-7066-40F0-91C7-7CB6A871BFDE}" machineid="{8BDE4C4D-9083-4D61-B41C-3253212C0C37}" oem="ec3000" version="ForcedUpdate" track="dev-channel" from_track="developer-build" lang="en-US" board="amd64-generic" hardware_class="" delta_okay="false" >
|
||||
<ping active="1" a="-1" r="-1"></ping>
|
||||
<updatecheck targetversionprefix=""></updatecheck>
|
||||
<event eventtype="3" eventresult="2" previousversion=""></event>
|
||||
</app>
|
||||
</request>
|
||||
`
|
||||
sampleResponse = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<response protocol="3.0">
|
||||
<daystart elapsed_seconds="49008"/>
|
||||
<app appid="{87efface-864d-49a5-9bb3-4b050a7c227a}" status="ok">
|
||||
<ping status="ok"/>
|
||||
<updatecheck status="ok">
|
||||
<urls>
|
||||
<url codebase="http://kam:8080/static/"/>
|
||||
</urls>
|
||||
<manifest version="9999.0.0">
|
||||
<packages>
|
||||
<package hash="+LXvjiaPkeYDLHoNKlf9qbJwvnk=" name="update.gz" size="67546213" required="true"/>
|
||||
</packages>
|
||||
<actions>
|
||||
<action event="postinstall" DisplayVersion="9999.0.0" sha256="0VAlQW3RE99SGtSB5R4m08antAHO8XDoBMKDyxQT/Mg=" needsadmin="false" IsDeltaPayload="true" />
|
||||
</actions>
|
||||
</manifest>
|
||||
</updatecheck>
|
||||
</app>
|
||||
</response>
|
||||
`
|
||||
)
|
||||
|
||||
func TestOmahaRequestUpdateCheck(t *testing.T) {
|
||||
v, err := ParseRequest("", strings.NewReader(sampleRequest))
|
||||
if err != nil {
|
||||
t.Fatalf("ParseRequest failed: %v", err)
|
||||
}
|
||||
|
||||
if v.OS.Version != "Indy" {
|
||||
t.Error("Unexpected version", v.OS.Version)
|
||||
}
|
||||
|
||||
if v.Apps[0].ID != "{87efface-864d-49a5-9bb3-4b050a7c227a}" {
|
||||
t.Error("Expected an App ID")
|
||||
}
|
||||
|
||||
if v.Apps[0].BootID != "{7D52A1CC-7066-40F0-91C7-7CB6A871BFDE}" {
|
||||
t.Error("Expected a Boot ID")
|
||||
}
|
||||
|
||||
if v.Apps[0].MachineID != "{8BDE4C4D-9083-4D61-B41C-3253212C0C37}" {
|
||||
t.Error("Expected a Machine ID")
|
||||
}
|
||||
|
||||
if v.Apps[0].OEM != "ec3000" {
|
||||
t.Error("Expected an OEM")
|
||||
}
|
||||
|
||||
if v.Apps[0].UpdateCheck == nil {
|
||||
t.Error("Expected an UpdateCheck")
|
||||
}
|
||||
|
||||
if v.Apps[0].Version != "ForcedUpdate" {
|
||||
t.Error("Verison is ForcedUpdate")
|
||||
}
|
||||
|
||||
if v.Apps[0].FromTrack != "developer-build" {
|
||||
t.Error("developer-build")
|
||||
}
|
||||
|
||||
if v.Apps[0].Track != "dev-channel" {
|
||||
t.Error("dev-channel")
|
||||
}
|
||||
|
||||
if v.Apps[0].Events[0].Type != EventTypeUpdateComplete {
|
||||
t.Error("Expected EventTypeUpdateComplete")
|
||||
}
|
||||
|
||||
if v.Apps[0].Events[0].Result != EventResultSuccessReboot {
|
||||
t.Error("Expected EventResultSuccessReboot")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOmahaResponseWithUpdate(t *testing.T) {
|
||||
parsed, err := ParseResponse("", strings.NewReader(sampleResponse))
|
||||
if err != nil {
|
||||
t.Fatalf("ParseResponse failed: %v", err)
|
||||
}
|
||||
|
||||
expected := &Response{
|
||||
XMLName: xml.Name{Local: "response"},
|
||||
Protocol: "3.0",
|
||||
DayStart: DayStart{ElapsedSeconds: "49008"},
|
||||
Apps: []*AppResponse{&AppResponse{
|
||||
ID: "{87efface-864d-49a5-9bb3-4b050a7c227a}",
|
||||
Status: AppOK,
|
||||
Ping: &PingResponse{"ok"},
|
||||
UpdateCheck: &UpdateResponse{
|
||||
Status: UpdateOK,
|
||||
URLs: []*URL{&URL{
|
||||
CodeBase: "http://kam:8080/static/",
|
||||
}},
|
||||
Manifest: &Manifest{
|
||||
Version: "9999.0.0",
|
||||
Packages: []*Package{&Package{
|
||||
SHA1: "+LXvjiaPkeYDLHoNKlf9qbJwvnk=",
|
||||
Name: "update.gz",
|
||||
Size: 67546213,
|
||||
Required: true,
|
||||
}},
|
||||
Actions: []*Action{&Action{
|
||||
Event: "postinstall",
|
||||
DisplayVersion: "9999.0.0",
|
||||
SHA256: "0VAlQW3RE99SGtSB5R4m08antAHO8XDoBMKDyxQT/Mg=",
|
||||
IsDeltaPayload: true,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(parsed, expected) {
|
||||
t.Errorf("parsed != expected\n%v\n%v", parsed, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOmahaResponsAsRequest(t *testing.T) {
|
||||
_, err := ParseRequest("", strings.NewReader(sampleResponse))
|
||||
if err == nil {
|
||||
t.Fatal("ParseRequest successfully parsed a response")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOmahaRequestAsResponse(t *testing.T) {
|
||||
_, err := ParseResponse("", strings.NewReader(sampleRequest))
|
||||
if err == nil {
|
||||
t.Fatal("ParseResponse successfully parsed a request")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleNewResponse() {
|
||||
response := NewResponse()
|
||||
app := response.AddApp("{52F1B9BC-D31A-4D86-9276-CBC256AADF9A}", "ok")
|
||||
app.AddPing()
|
||||
u := app.AddUpdateCheck(UpdateOK)
|
||||
u.AddURL("http://localhost/updates")
|
||||
m := u.AddManifest("9999.0.0")
|
||||
k := m.AddPackage()
|
||||
k.SHA1 = "+LXvjiaPkeYDLHoNKlf9qbJwvnk="
|
||||
k.Name = "update.gz"
|
||||
k.Size = 67546213
|
||||
k.Required = true
|
||||
a := m.AddAction("postinstall")
|
||||
a.DisplayVersion = "9999.0.0"
|
||||
a.SHA256 = "0VAlQW3RE99SGtSB5R4m08antAHO8XDoBMKDyxQT/Mg="
|
||||
a.NeedsAdmin = false
|
||||
a.IsDeltaPayload = true
|
||||
a.DisablePayloadBackoff = true
|
||||
|
||||
if raw, err := xml.MarshalIndent(response, "", " "); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
} else {
|
||||
fmt.Printf("%s%s\n", xml.Header, raw)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// <?xml version="1.0" encoding="UTF-8"?>
|
||||
// <response protocol="3.0" server="go-omaha">
|
||||
// <daystart elapsed_seconds="0"></daystart>
|
||||
// <app appid="{52F1B9BC-D31A-4D86-9276-CBC256AADF9A}" status="ok">
|
||||
// <ping status="ok"></ping>
|
||||
// <updatecheck status="ok">
|
||||
// <urls>
|
||||
// <url codebase="http://localhost/updates"></url>
|
||||
// </urls>
|
||||
// <manifest version="9999.0.0">
|
||||
// <packages>
|
||||
// <package name="update.gz" hash="+LXvjiaPkeYDLHoNKlf9qbJwvnk=" size="67546213" required="true"></package>
|
||||
// </packages>
|
||||
// <actions>
|
||||
// <action event="postinstall" DisplayVersion="9999.0.0" sha256="0VAlQW3RE99SGtSB5R4m08antAHO8XDoBMKDyxQT/Mg=" IsDeltaPayload="true" DisablePayloadBackoff="true"></action>
|
||||
// </actions>
|
||||
// </manifest>
|
||||
// </updatecheck>
|
||||
// </app>
|
||||
// </response>
|
||||
}
|
||||
|
||||
func ExampleNewRequest() {
|
||||
request := NewRequest()
|
||||
request.Version = ""
|
||||
request.OS = &OS{
|
||||
Platform: "Chrome OS",
|
||||
Version: "Indy",
|
||||
ServicePack: "ForcedUpdate_x86_64",
|
||||
}
|
||||
app := request.AddApp("{27BD862E-8AE8-4886-A055-F7F1A6460627}", "1.0.0.0")
|
||||
app.AddUpdateCheck()
|
||||
|
||||
event := app.AddEvent()
|
||||
event.Type = EventTypeDownloadComplete
|
||||
event.Result = EventResultError
|
||||
|
||||
if raw, err := xml.MarshalIndent(request, "", " "); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
} else {
|
||||
fmt.Printf("%s%s\n", xml.Header, raw)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// <?xml version="1.0" encoding="UTF-8"?>
|
||||
// <request protocol="3.0">
|
||||
// <os platform="Chrome OS" version="Indy" sp="ForcedUpdate_x86_64"></os>
|
||||
// <app appid="{27BD862E-8AE8-4886-A055-F7F1A6460627}" version="1.0.0.0">
|
||||
// <updatecheck></updatecheck>
|
||||
// <event eventtype="1" eventresult="0"></event>
|
||||
// </app>
|
||||
// </request>
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package omaha
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func NewServer(addr string, updater Updater) (*Server, error) {
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
Updater: updater,
|
||||
Mux: mux,
|
||||
l: l,
|
||||
srv: srv,
|
||||
}
|
||||
|
||||
h := &OmahaHandler{s}
|
||||
mux.Handle("/v1/update", h)
|
||||
mux.Handle("/v1/update/", h)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
Updater
|
||||
|
||||
Mux *http.ServeMux
|
||||
|
||||
l net.Listener
|
||||
srv *http.Server
|
||||
}
|
||||
|
||||
func (s *Server) Serve() error {
|
||||
err := s.srv.Serve(s.l)
|
||||
if isClosed(err) {
|
||||
// gracefully quit
|
||||
err = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) Destroy() error {
|
||||
return s.l.Close()
|
||||
}
|
||||
|
||||
func (s *Server) Addr() net.Addr {
|
||||
return s.l.Addr()
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package omaha
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type mockServer struct {
|
||||
UpdaterStub
|
||||
|
||||
reqChan chan *Request
|
||||
}
|
||||
|
||||
func (m *mockServer) CheckApp(req *Request, app *AppRequest) error {
|
||||
m.reqChan <- req
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestServerRequestResponse(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
// make an omaha server
|
||||
svc := &mockServer{
|
||||
reqChan: make(chan *Request),
|
||||
}
|
||||
|
||||
s, err := NewServer("127.0.0.1:0", svc)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create omaha server: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
err := s.Destroy()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
close(svc.reqChan)
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := s.Serve(); err != nil {
|
||||
t.Errorf("Serve failed: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
enc := xml.NewEncoder(buf)
|
||||
enc.Indent("", "\t")
|
||||
err = enc.Encode(nilRequest)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal request: %v", err)
|
||||
}
|
||||
|
||||
// check that server gets the same thing we sent
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
sreq, ok := <-svc.reqChan
|
||||
if !ok {
|
||||
t.Errorf("failed to get notification from server")
|
||||
return
|
||||
}
|
||||
|
||||
if err := compareXML(nilRequest, sreq); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// send omaha request
|
||||
endpoint := fmt.Sprintf("http://%s/v1/update/", s.Addr())
|
||||
httpClient := &http.Client{
|
||||
Timeout: 2 * time.Second,
|
||||
}
|
||||
res, err := httpClient.Post(endpoint, "text/xml", buf)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to post: %v", err)
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
t.Fatalf("failed to post: %v", res.Status)
|
||||
}
|
||||
|
||||
dec := xml.NewDecoder(res.Body)
|
||||
sresp := &Response{}
|
||||
if err := dec.Decode(sresp); err != nil {
|
||||
t.Fatalf("failed to parse body: %v", err)
|
||||
}
|
||||
if err := compareXML(nilResponse, sresp); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package omaha
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Translate GOARCH to Omaha's choice of names, because no two independent
|
||||
// software projects *ever* use the same set of architecture names. ;-)
|
||||
func LocalArch() string {
|
||||
switch runtime.GOARCH {
|
||||
case "386":
|
||||
return "x86"
|
||||
case "amd64":
|
||||
return "x64"
|
||||
case "amd64p32":
|
||||
// Not actually specified by Omaha but it follows the above.
|
||||
return "x32"
|
||||
case "arm":
|
||||
fallthrough
|
||||
default:
|
||||
// Nothing else is defined by Omaha so anything goes.
|
||||
return runtime.GOARCH
|
||||
}
|
||||
}
|
||||
|
||||
// Translate GOOS to Omaha's platform names as best as we can.
|
||||
func LocalPlatform() string {
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
return "mac" // or "ios"
|
||||
case "linux":
|
||||
return "linux" // or "android"
|
||||
case "windows":
|
||||
return "win"
|
||||
default:
|
||||
// Nothing else is defined by Omaha so anything goes.
|
||||
return runtime.GOOS
|
||||
}
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package omaha
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/blang/semver"
|
||||
)
|
||||
|
||||
const pkg_prefix = "/packages/"
|
||||
|
||||
// trivialUpdater always responds with the given Update.
|
||||
type trivialUpdater struct {
|
||||
UpdaterStub
|
||||
Update
|
||||
}
|
||||
|
||||
func (tu *trivialUpdater) CheckUpdate(req *Request, app *AppRequest) (*Update, error) {
|
||||
if len(tu.Manifest.Packages) == 0 {
|
||||
return nil, NoUpdate
|
||||
}
|
||||
|
||||
v1, err := semver.Make(app.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v2, err := semver.Make(tu.Manifest.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if v1.LT(v2) {
|
||||
return &tu.Update, nil
|
||||
}
|
||||
|
||||
return nil, NoUpdate
|
||||
}
|
||||
|
||||
// trivialHandler serves up a single file.
|
||||
type trivialHandler struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
func (th *trivialHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if th.Path == "" {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
http.ServeFile(w, r, th.Path)
|
||||
}
|
||||
|
||||
// TrivialServer is an extremely basic Omaha server that ignores all
|
||||
// incoming metadata, always responding with the same update response.
|
||||
// The update is constructed by calling AddPackage one or more times.
|
||||
type TrivialServer struct {
|
||||
*Server
|
||||
tu trivialUpdater
|
||||
}
|
||||
|
||||
func NewTrivialServer(addr string) (*TrivialServer, error) {
|
||||
ts := TrivialServer{
|
||||
tu: trivialUpdater{
|
||||
Update: Update{
|
||||
URL: URL{CodeBase: pkg_prefix},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
s, err := NewServer(addr, &ts.tu)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ts.Server = s
|
||||
|
||||
return &ts, nil
|
||||
}
|
||||
|
||||
// AddPackage adds a new file to the update response.
|
||||
// file is the local filesystem path, name is the final URL component.
|
||||
func (ts *TrivialServer) AddPackage(file, name string) error {
|
||||
// name may not include any path components
|
||||
if path.Base(name) != name || name[0] == '.' {
|
||||
return fmt.Errorf("invalid package name %q", name)
|
||||
}
|
||||
|
||||
pkg, err := ts.tu.Manifest.AddPackageFromPath(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pkg.Name = name
|
||||
|
||||
// Insert the update_engine style postinstall action if
|
||||
// this is the first (and probably only) package.
|
||||
if len(ts.tu.Manifest.Actions) == 0 {
|
||||
act := ts.tu.Manifest.AddAction("postinstall")
|
||||
act.DisablePayloadBackoff = true
|
||||
act.SHA256 = pkg.SHA256
|
||||
}
|
||||
|
||||
ts.Mux.Handle(pkg_prefix+name, &trivialHandler{file})
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetVersion sets the manifest's version with the provided one.
|
||||
func (ts *TrivialServer) SetVersion(version string) {
|
||||
ts.tu.Manifest.Version = version
|
||||
}
|
|
@ -1,140 +0,0 @@
|
|||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package omaha
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func mkUpdateReq() (*bytes.Buffer, error) {
|
||||
req := NewRequest()
|
||||
app := req.AddApp(testAppID, testAppVer)
|
||||
app.AddUpdateCheck()
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
enc := xml.NewEncoder(buf)
|
||||
enc.Indent("", "\t")
|
||||
if err := enc.Encode(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func TestTrivialServer(t *testing.T) {
|
||||
tmp, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer tmp.Close()
|
||||
defer os.Remove(tmp.Name())
|
||||
|
||||
if _, err := tmp.WriteString("test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s, err := NewTrivialServer(":0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Destroy()
|
||||
if err := s.AddPackage(tmp.Name(), "update.gz"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s.SetVersion(testAppVer)
|
||||
go s.Serve()
|
||||
|
||||
buf, err := mkUpdateReq()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
endpoint := fmt.Sprintf("http://%s/v1/update/", s.Addr())
|
||||
res, err := http.Post(endpoint, "text/xml", buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != 200 {
|
||||
t.Fatalf("failed to post: %v", res.Status)
|
||||
}
|
||||
|
||||
dec := xml.NewDecoder(res.Body)
|
||||
resp := &Response{}
|
||||
if err := dec.Decode(resp); err != nil {
|
||||
t.Fatalf("failed to parse body: %v", err)
|
||||
}
|
||||
|
||||
// Should get zero update because the version is already the latest.
|
||||
if len(resp.Apps) != 1 ||
|
||||
resp.Apps[0].UpdateCheck == nil ||
|
||||
resp.Apps[0].UpdateCheck.Status != NoUpdate {
|
||||
t.Fatalf("unexpected response: %#v", resp)
|
||||
}
|
||||
|
||||
// Should get an update now.
|
||||
s.SetVersion("999.999.999")
|
||||
|
||||
buf, err = mkUpdateReq()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
endpoint = fmt.Sprintf("http://%s/v1/update/", s.Addr())
|
||||
res, err = http.Post(endpoint, "text/xml", buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != 200 {
|
||||
t.Fatalf("failed to post: %v", res.Status)
|
||||
}
|
||||
|
||||
dec = xml.NewDecoder(res.Body)
|
||||
resp = &Response{}
|
||||
if err := dec.Decode(resp); err != nil {
|
||||
t.Fatalf("failed to parse body: %v", err)
|
||||
}
|
||||
|
||||
if len(resp.Apps) != 1 ||
|
||||
resp.Apps[0].UpdateCheck == nil ||
|
||||
resp.Apps[0].UpdateCheck.Status != UpdateOK ||
|
||||
len(resp.Apps[0].UpdateCheck.URLs) != 1 ||
|
||||
resp.Apps[0].UpdateCheck.Manifest == nil ||
|
||||
len(resp.Apps[0].UpdateCheck.Manifest.Packages) != 1 {
|
||||
t.Fatalf("unexpected response: %#v", resp)
|
||||
}
|
||||
|
||||
pkgres, err := http.Get(resp.Apps[0].UpdateCheck.URLs[0].CodeBase +
|
||||
resp.Apps[0].UpdateCheck.Manifest.Packages[0].Name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pkgdata, err := ioutil.ReadAll(pkgres.Body)
|
||||
pkgres.Body.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(pkgdata) != "test" {
|
||||
t.Fatalf("unexpected package data: %q", string(pkgdata))
|
||||
}
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package omaha
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
// Update is a manifest for a single omaha update response. It extends
|
||||
// the standard Manifest protocol element with the application id and
|
||||
// previous version which are used to match against the update request.
|
||||
// A blank previous version indicates this update can be applied to any
|
||||
// existing install. The application id may not be blank.
|
||||
type Update struct {
|
||||
XMLName xml.Name `xml:"update" json:"-"`
|
||||
ID string `xml:"appid,attr"`
|
||||
PreviousVersion string `xml:"previousversion,attr,omitempty"`
|
||||
URL URL `xml:"urls>url"`
|
||||
Manifest
|
||||
|
||||
// The delta_okay request attribute is an update_engine extension.
|
||||
RespectDeltaOK bool `xml:"respect_delta_okay,attr,omitempty"`
|
||||
}
|
||||
|
||||
// The URL attribute in Update is currently assumed to be a relative
|
||||
// path which may be found on multiple mirrors. A server using this is
|
||||
// expected to know the mirror prefix(s) it can give the client.
|
||||
func (u *Update) URLs(prefixes []string) []*URL {
|
||||
urls := make([]*URL, len(prefixes))
|
||||
for i, prefix := range prefixes {
|
||||
urls[i] = &URL{CodeBase: prefix + u.URL.CodeBase}
|
||||
}
|
||||
return urls
|
||||
}
|
||||
|
||||
// Updater provides a common interface for any backend that can respond to
|
||||
// update requests made to an Omaha server.
|
||||
type Updater interface {
|
||||
CheckApp(req *Request, app *AppRequest) error
|
||||
CheckUpdate(req *Request, app *AppRequest) (*Update, error)
|
||||
Event(req *Request, app *AppRequest, event *EventRequest)
|
||||
Ping(req *Request, app *AppRequest)
|
||||
}
|
||||
|
||||
type UpdaterStub struct{}
|
||||
|
||||
func (u UpdaterStub) CheckApp(req *Request, app *AppRequest) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u UpdaterStub) CheckUpdate(req *Request, app *AppRequest) (*Update, error) {
|
||||
return nil, NoUpdate
|
||||
}
|
||||
|
||||
func (u UpdaterStub) Event(req *Request, app *AppRequest, event *EventRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
func (u UpdaterStub) Ping(req *Request, app *AppRequest) {
|
||||
return
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package omaha
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const SampleUpdate = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<update appid="{87efface-864d-49a5-9bb3-4b050a7c227a}" version="9999.0.0">
|
||||
<urls>
|
||||
<url codebase="packages/9999.0.0"></url>
|
||||
</urls>
|
||||
<manifest version="9999.0.0">
|
||||
<packages>
|
||||
<package name="update.gz" hash="+LXvjiaPkeYDLHoNKlf9qbJwvnk=" size="67546213" required="true"></package>
|
||||
</packages>
|
||||
</update>
|
||||
`
|
||||
|
||||
func TestUpdateURLs(t *testing.T) {
|
||||
u := Update{}
|
||||
xml.Unmarshal([]byte(SampleUpdate), &u)
|
||||
|
||||
urls := u.URLs([]string{"http://localhost/updates/"})
|
||||
if urls[0].CodeBase != "http://localhost/updates/packages/9999.0.0" {
|
||||
t.Error("Unexpected URL", urls[0].CodeBase)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue