Merge pull request #18 from marineam/from-mantle
Import omaha updates from mantle
This commit is contained in:
commit
c694d6ed59
18 changed files with 1662 additions and 402 deletions
|
@ -1,5 +1,8 @@
|
||||||
language: go
|
language: go
|
||||||
go: 1.1
|
sudo: false
|
||||||
|
go:
|
||||||
|
- 1.7.5
|
||||||
|
- 1.8.1
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- go test -v ./...
|
- go test -v ./...
|
||||||
|
|
181
omaha/codes.go
Normal file
181
omaha/codes.go
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
// 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 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 string(u)
|
||||||
|
}
|
134
omaha/handler.go
Normal file
134
omaha/handler.go
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
decoder := xml.NewDecoder(reader)
|
||||||
|
var omahaReq Request
|
||||||
|
if err := decoder.Decode(&omahaReq); err != nil {
|
||||||
|
log.Printf("omaha: Failed decoding XML: %v", err)
|
||||||
|
http.Error(w, "Invalid XML", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if omahaReq.Protocol != "3.0" {
|
||||||
|
log.Printf("omaha: Unexpected protocol: %q", omahaReq.Protocol)
|
||||||
|
http.Error(w, "Omaha 3.0 Required", 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
|
||||||
|
}
|
68
omaha/handler_test.go
Normal file
68
omaha/handler_test.go
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
32
omaha/isclosed.go
Normal file
32
omaha/isclosed.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// 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"
|
||||||
|
}
|
264
omaha/omaha.go
264
omaha/omaha.go
|
@ -1,264 +0,0 @@
|
||||||
/*
|
|
||||||
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.
|
|
||||||
https://github.com/google/omaha/blob/wiki/ServerProtocol.md
|
|
||||||
*/
|
|
||||||
package omaha
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/xml"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Request struct {
|
|
||||||
XMLName xml.Name `xml:"request" datastore:"-"`
|
|
||||||
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" datastore:"-" json:"-"`
|
|
||||||
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" datastore"-" json:"-"`
|
|
||||||
Ping *Ping `xml:"ping"`
|
|
||||||
UpdateCheck *UpdateCheck `xml:"updatecheck"`
|
|
||||||
Events []*Event `xml:"event" json:",omitempty"`
|
|
||||||
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"`
|
|
||||||
Status string `xml:"status,attr,omitempty"`
|
|
||||||
|
|
||||||
// update engine extensions
|
|
||||||
Track string `xml:"track,attr,omitempty"`
|
|
||||||
FromTrack string `xml:"from_track,attr,omitempty"`
|
|
||||||
|
|
||||||
// coreos update engine extensions
|
|
||||||
BootId string `xml:"bootid,attr,omitempty"`
|
|
||||||
MachineID string `xml:"machineid,attr,omitempty"`
|
|
||||||
OEM string `xml:"oem,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" datastore:"-" json:"-"`
|
|
||||||
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" datastore:"-" json:"-"`
|
|
||||||
LastReportDays string `xml:"r,attr,omitempty"`
|
|
||||||
Status string `xml:"status,attr,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Os struct {
|
|
||||||
XMLName xml.Name `xml:"os" datastore:"-" json:"-"`
|
|
||||||
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" datastore:"-" json:"-"`
|
|
||||||
Type string `xml:"eventtype,attr,omitempty"`
|
|
||||||
Result string `xml:"eventresult,attr,omitempty"`
|
|
||||||
PreviousVersion string `xml:"previousversion,attr,omitempty"`
|
|
||||||
ErrorCode string `xml:"errorcode,attr,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Urls struct {
|
|
||||||
XMLName xml.Name `xml:"urls" datastore:"-" json:"-"`
|
|
||||||
Urls []Url `xml:"url" json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Url struct {
|
|
||||||
XMLName xml.Name `xml:"url" datastore:"-" json:"-"`
|
|
||||||
CodeBase string `xml:"codebase,attr"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Manifest struct {
|
|
||||||
XMLName xml.Name `xml:"manifest" datastore:"-" json:"-"`
|
|
||||||
Packages Packages `xml:"packages"`
|
|
||||||
Actions Actions `xml:"actions"`
|
|
||||||
Version string `xml:"version,attr"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Packages struct {
|
|
||||||
XMLName xml.Name `xml:"packages" datastore:"-" json:"-"`
|
|
||||||
Packages []Package `xml:"package" json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Package struct {
|
|
||||||
XMLName xml.Name `xml:"package" datastore:"-" json:"-"`
|
|
||||||
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" datastore:"-" json:"-"`
|
|
||||||
Actions []*Action `xml:"action" json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Action struct {
|
|
||||||
XMLName xml.Name `xml:"action" datastore:"-" json:"-"`
|
|
||||||
Event string `xml:"event,attr"`
|
|
||||||
|
|
||||||
// Extensions added by update_engine
|
|
||||||
ChromeOSVersion string `xml:"ChromeOSVersion,attr"`
|
|
||||||
Sha256 string `xml:"sha256,attr"`
|
|
||||||
NeedsAdmin bool `xml:"needsadmin,attr"`
|
|
||||||
IsDelta bool `xml:"IsDelta,attr"`
|
|
||||||
DisablePayloadBackoff bool `xml:"DisablePayloadBackoff,attr,omitempty"`
|
|
||||||
MetadataSignatureRsa string `xml:"MetadataSignatureRsa,attr,omitempty"`
|
|
||||||
MetadataSize string `xml:"MetadataSize,attr,omitempty"`
|
|
||||||
Deadline string `xml:"deadline,attr,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
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",
|
|
||||||
800: "ping",
|
|
||||||
}
|
|
||||||
|
|
||||||
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",
|
|
||||||
}
|
|
|
@ -1,137 +0,0 @@
|
||||||
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].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 MachineId")
|
|
||||||
}
|
|
||||||
|
|
||||||
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 != "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
|
|
||||||
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="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" DisablePayloadBackoff="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>
|
|
||||||
}
|
|
112
omaha/package.go
Normal file
112
omaha/package.go
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
// 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. The Sha256 attribute
|
||||||
|
// is not a standard part of the Omaha protocol which only uses Sha1.
|
||||||
|
type Package struct {
|
||||||
|
Name string `xml:"name,attr"`
|
||||||
|
Sha1 string `xml:"hash,attr"`
|
||||||
|
Sha256 string `xml:"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 protocol extension.
|
||||||
|
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
|
||||||
|
}
|
144
omaha/package_test.go
Normal file
144
omaha/package_test.go
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
261
omaha/protocol.go
Normal file
261
omaha/protocol.go
Normal file
|
@ -0,0 +1,261 @@
|
||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
Version string `xml:"version,attr,omitempty"`
|
||||||
|
IsMachine string `xml:"ismachine,attr,omitempty"`
|
||||||
|
RequestId string `xml:"requestid,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"`
|
||||||
|
|
||||||
|
// 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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) AddApp(id, version string) *AppRequest {
|
||||||
|
a := &AppRequest{Id: id, Version: version}
|
||||||
|
r.Apps = append(r.Apps, a)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppRequest struct {
|
||||||
|
Ping *PingRequest `xml:"ping"`
|
||||||
|
UpdateCheck *UpdateRequest `xml:"updatecheck"`
|
||||||
|
Events []*EventRequest `xml:"event" json:",omitempty"`
|
||||||
|
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"`
|
||||||
|
|
||||||
|
// update engine extensions
|
||||||
|
Track string `xml:"track,attr,omitempty"`
|
||||||
|
FromTrack string `xml:"from_track,attr,omitempty"`
|
||||||
|
Board string `xml:"board,attr,omitempty"`
|
||||||
|
DeltaOK bool `xml:"delta_okay,attr,omitempty"`
|
||||||
|
|
||||||
|
// coreos update engine extensions
|
||||||
|
BootId string `xml:"bootid,attr,omitempty"`
|
||||||
|
MachineID string `xml:"machineid,attr,omitempty"`
|
||||||
|
OEM string `xml:"oem,attr,omitempty"`
|
||||||
|
OEMVersion string `xml:"oemversion,attr,omitempty"`
|
||||||
|
AlephVersion string `xml:"alephversion,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"`
|
||||||
|
NextVersion string `xml:"nextversion,attr,omitempty"`
|
||||||
|
PreviousVersion string `xml:"previousversion,attr,omitempty"`
|
||||||
|
ErrorCode string `xml:"errorcode,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"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Event struct {
|
||||||
|
Type EventType `xml:"eventtype,attr"`
|
||||||
|
Result EventResult `xml:"eventresult,attr"`
|
||||||
|
PreviousVersion string `xml:"previousversion,attr,omitempty"`
|
||||||
|
ErrorCode string `xml:"errorcode,attr,omitempty"`
|
||||||
|
Status string `xml:"status,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"`
|
||||||
|
}
|
163
omaha/protocol_test.go
Normal file
163
omaha/protocol_test.go
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 TestOmahaRequestUpdateCheck(t *testing.T) {
|
||||||
|
v := Request{}
|
||||||
|
xml.Unmarshal([]byte(SampleRequest), &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].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 MachineId")
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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>
|
||||||
|
}
|
73
omaha/server.go
Normal file
73
omaha/server.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
// 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()
|
||||||
|
}
|
114
omaha/server_test.go
Normal file
114
omaha/server_test.go
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
53
omaha/system.go
Normal file
53
omaha/system.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
100
omaha/trivial_server.go
Normal file
100
omaha/trivial_server.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
return &tu.Update, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
108
omaha/trivial_server_test.go
Normal file
108
omaha/trivial_server_test.go
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
73
omaha/update.go
Normal file
73
omaha/update.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
// 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
|
||||||
|
}
|
42
omaha/update_test.go
Normal file
42
omaha/update_test.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
// 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