omaha: rework/cleanup protocol APIs

- Avoid long argument lists and only pass values that are strictly
   required such as status, fill in all other fields directly instead.
 - Fill OS struct in requests based on local system.
 - Define event and status codes as constants.
 - Misc style tweaks.
This commit is contained in:
Michael Marineau 2015-07-23 23:14:36 -07:00
parent fef283aeb6
commit ec70842bdd
4 changed files with 308 additions and 121 deletions

170
omaha/codes.go Normal file
View file

@ -0,0 +1,170 @@
// 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"
)
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"
)

View file

@ -24,11 +24,13 @@ package omaha
import ( import (
"encoding/xml" "encoding/xml"
"github.com/coreos/mantle/version"
) )
type Request struct { type Request struct {
XMLName xml.Name `xml:"request" json:"-"` XMLName xml.Name `xml:"request" json:"-"`
Os Os `xml:"os"` OS *OS `xml:"os"`
Apps []*App `xml:"app"` Apps []*App `xml:"app"`
Protocol string `xml:"protocol,attr"` Protocol string `xml:"protocol,attr"`
Version string `xml:"version,attr,omitempty"` Version string `xml:"version,attr,omitempty"`
@ -41,16 +43,20 @@ type Request struct {
UpdaterVersion string `xml:"updaterversion,attr,omitempty"` UpdaterVersion string `xml:"updaterversion,attr,omitempty"`
} }
func NewRequest(version string, platform string, sp string, arch string) *Request { func NewRequest() *Request {
r := new(Request) return &Request{
r.Protocol = "3.0" Protocol: "3.0",
r.Os = Os{Version: version, Platform: platform, Sp: sp, Arch: arch} Version: version.Version,
return r OS: &OS{
Platform: LocalPlatform(),
Arch: LocalArch(),
// TODO(marineam): Version and ServicePack
},
}
} }
func (r *Request) AddApp(id string, version string) *App { func (r *Request) AddApp(id, version string) *App {
a := NewApp(id) a := &App{Id: id, Version: version}
a.Version = version
r.Apps = append(r.Apps, a) r.Apps = append(r.Apps, a)
return a return a
} }
@ -63,18 +69,20 @@ type Response struct {
Server string `xml:"server,attr"` Server string `xml:"server,attr"`
} }
func NewResponse(server string) *Response { func NewResponse() *Response {
r := &Response{Server: server, Protocol: "3.0"} return &Response{
r.DayStart.ElapsedSeconds = "0" Protocol: "3.0",
return r Server: "mantle",
DayStart: DayStart{ElapsedSeconds: "0"},
}
} }
type DayStart struct { type DayStart struct {
ElapsedSeconds string `xml:"elapsed_seconds,attr"` ElapsedSeconds string `xml:"elapsed_seconds,attr"`
} }
func (r *Response) AddApp(id string) *App { func (r *Response) AddApp(id string, status AppStatus) *App {
a := NewApp(id) a := &App{Id: id, Status: status}
r.Apps = append(r.Apps, a) r.Apps = append(r.Apps, a)
return a return a
} }
@ -89,7 +97,7 @@ type App struct {
Lang string `xml:"lang,attr,omitempty"` Lang string `xml:"lang,attr,omitempty"`
Client string `xml:"client,attr,omitempty"` Client string `xml:"client,attr,omitempty"`
InstallAge string `xml:"installage,attr,omitempty"` InstallAge string `xml:"installage,attr,omitempty"`
Status string `xml:"status,attr,omitempty"` Status AppStatus `xml:"status,attr,omitempty"`
// update engine extensions // update engine extensions
Track string `xml:"track,attr,omitempty"` Track string `xml:"track,attr,omitempty"`
@ -101,11 +109,6 @@ type App struct {
OEM string `xml:"oem,attr,omitempty"` OEM string `xml:"oem,attr,omitempty"`
} }
func NewApp(id string) *App {
a := &App{Id: id}
return a
}
func (a *App) AddUpdateCheck() *UpdateCheck { func (a *App) AddUpdateCheck() *UpdateCheck {
a.UpdateCheck = new(UpdateCheck) a.UpdateCheck = new(UpdateCheck)
return a.UpdateCheck return a.UpdateCheck
@ -123,19 +126,20 @@ func (a *App) AddEvent() *Event {
} }
type UpdateCheck struct { type UpdateCheck struct {
Urls *Urls `xml:"urls"` URLs *URLs `xml:"urls"`
Manifest *Manifest `xml:"manifest"` Manifest *Manifest `xml:"manifest"`
TargetVersionPrefix string `xml:"targetversionprefix,attr,omitempty"` TargetVersionPrefix string `xml:"targetversionprefix,attr,omitempty"`
Status string `xml:"status,attr,omitempty"` Status UpdateStatus `xml:"status,attr,omitempty"`
} }
func (u *UpdateCheck) AddUrl(codebase string) *Url { func (u *UpdateCheck) AddURL(codebase string) *URL {
if u.Urls == nil { // An intermediate struct is used instead of a "urls>url" tag simply
u.Urls = new(Urls) // to keep Go from generating <urls></urls> if the list is empty.
if u.URLs == nil {
u.URLs = new(URLs)
} }
url := new(Url) url := &URL{CodeBase: codebase}
url.CodeBase = codebase u.URLs.URLs = append(u.URLs.URLs, url)
u.Urls.Urls = append(u.Urls.Urls, *url)
return url return url
} }
@ -149,58 +153,52 @@ type Ping struct {
Status string `xml:"status,attr,omitempty"` Status string `xml:"status,attr,omitempty"`
} }
type Os struct { type OS struct {
Platform string `xml:"platform,attr,omitempty"` Platform string `xml:"platform,attr,omitempty"`
Version string `xml:"version,attr,omitempty"` Version string `xml:"version,attr,omitempty"`
Sp string `xml:"sp,attr,omitempty"` ServicePack string `xml:"sp,attr,omitempty"`
Arch string `xml:"arch,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 { type Event struct {
Type string `xml:"eventtype,attr,omitempty"` Type EventType `xml:"eventtype,attr"`
Result string `xml:"eventresult,attr,omitempty"` Result EventResult `xml:"eventresult,attr"`
PreviousVersion string `xml:"previousversion,attr,omitempty"` PreviousVersion string `xml:"previousversion,attr,omitempty"`
ErrorCode string `xml:"errorcode,attr,omitempty"` ErrorCode string `xml:"errorcode,attr,omitempty"`
Status string `xml:"status,attr,omitempty"`
} }
type Urls struct { type URLs struct {
Urls []Url `xml:"url" json:",omitempty"` URLs []*URL `xml:"url" json:",omitempty"`
} }
type Url struct { type URL struct {
CodeBase string `xml:"codebase,attr"` CodeBase string `xml:"codebase,attr"`
} }
type Manifest struct { type Manifest struct {
Packages Packages `xml:"packages"` Packages []*Package `xml:"packages>package"`
Actions Actions `xml:"actions"` Actions []*Action `xml:"actions>action"`
Version string `xml:"version,attr"` Version string `xml:"version,attr"`
}
type Packages struct {
Packages []Package `xml:"package" json:",omitempty"`
} }
type Package struct { type Package struct {
Hash string `xml:"hash,attr"` Hash string `xml:"hash,attr"`
Name string `xml:"name,attr"` Name string `xml:"name,attr"`
Size string `xml:"size,attr"` Size uint64 `xml:"size,attr"`
Required bool `xml:"required,attr"` Required bool `xml:"required,attr"`
} }
func (m *Manifest) AddPackage(hash string, name string, size string, required bool) *Package { func (m *Manifest) AddPackage() *Package {
p := &Package{Hash: hash, Name: name, Size: size, Required: required} p := &Package{}
m.Packages.Packages = append(m.Packages.Packages, *p) m.Packages = append(m.Packages, p)
return p return p
} }
type Actions struct { func (m *Manifest) AddAction(event string) *Action {
Actions []*Action `xml:"action" json:",omitempty"` a := &Action{Event: event}
m.Actions = append(m.Actions, a)
return a
} }
type Action struct { type Action struct {
@ -216,50 +214,3 @@ type Action struct {
MetadataSize string `xml:"MetadataSize,attr,omitempty"` MetadataSize string `xml:"MetadataSize,attr,omitempty"`
Deadline string `xml:"deadline,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",
}

View file

@ -35,8 +35,8 @@ func TestOmahaRequestUpdateCheck(t *testing.T) {
v := Request{} v := Request{}
xml.Unmarshal([]byte(SampleRequest), &v) xml.Unmarshal([]byte(SampleRequest), &v)
if v.Os.Version != "Indy" { if v.OS.Version != "Indy" {
t.Error("Unexpected version", v.Os.Version) t.Error("Unexpected version", v.OS.Version)
} }
if v.Apps[0].Id != "{87efface-864d-49a5-9bb3-4b050a7c227a}" { if v.Apps[0].Id != "{87efface-864d-49a5-9bb3-4b050a7c227a}" {
@ -71,22 +71,29 @@ func TestOmahaRequestUpdateCheck(t *testing.T) {
t.Error("dev-channel") t.Error("dev-channel")
} }
if v.Apps[0].Events[0].Type != "3" { if v.Apps[0].Events[0].Type != EventTypeUpdateComplete {
t.Error("developer-build") t.Error("Expected EventTypeUpdateComplete")
}
if v.Apps[0].Events[0].Result != EventResultSuccessReboot {
t.Error("Expected EventResultSuccessReboot")
} }
} }
func ExampleNewResponse() { func ExampleNewResponse() {
response := NewResponse("unit-test") response := NewResponse()
app := response.AddApp("{52F1B9BC-D31A-4D86-9276-CBC256AADF9A}") app := response.AddApp("{52F1B9BC-D31A-4D86-9276-CBC256AADF9A}", "ok")
app.Status = "ok"
p := app.AddPing() p := app.AddPing()
p.Status = "ok" p.Status = "ok"
u := app.AddUpdateCheck() u := app.AddUpdateCheck()
u.Status = "ok" u.Status = "ok"
u.AddUrl("http://localhost/updates") u.AddURL("http://localhost/updates")
m := u.AddManifest("9999.0.0") m := u.AddManifest("9999.0.0")
m.AddPackage("+LXvjiaPkeYDLHoNKlf9qbJwvnk=", "update.gz", "67546213", true) k := m.AddPackage()
k.Hash = "+LXvjiaPkeYDLHoNKlf9qbJwvnk="
k.Name = "update.gz"
k.Size = 67546213
k.Required = true
a := m.AddAction("postinstall") a := m.AddAction("postinstall")
a.ChromeOSVersion = "9999.0.0" a.ChromeOSVersion = "9999.0.0"
a.Sha256 = "0VAlQW3RE99SGtSB5R4m08antAHO8XDoBMKDyxQT/Mg=" a.Sha256 = "0VAlQW3RE99SGtSB5R4m08antAHO8XDoBMKDyxQT/Mg="
@ -103,7 +110,7 @@ func ExampleNewResponse() {
// Output: // Output:
// <?xml version="1.0" encoding="UTF-8"?> // <?xml version="1.0" encoding="UTF-8"?>
// <response protocol="3.0" server="unit-test"> // <response protocol="3.0" server="mantle">
// <daystart elapsed_seconds="0"></daystart> // <daystart elapsed_seconds="0"></daystart>
// <app appid="{52F1B9BC-D31A-4D86-9276-CBC256AADF9A}" status="ok"> // <app appid="{52F1B9BC-D31A-4D86-9276-CBC256AADF9A}" status="ok">
// <ping status="ok"></ping> // <ping status="ok"></ping>
@ -125,13 +132,19 @@ func ExampleNewResponse() {
} }
func ExampleNewRequest() { func ExampleNewRequest() {
request := NewRequest("Indy", "Chrome OS", "ForcedUpdate_x86_64", "") 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 := request.AddApp("{27BD862E-8AE8-4886-A055-F7F1A6460627}", "1.0.0.0")
app.AddUpdateCheck() app.AddUpdateCheck()
event := app.AddEvent() event := app.AddEvent()
event.Type = "1" event.Type = EventTypeDownloadComplete
event.Result = "0" event.Result = EventResultError
if raw, err := xml.MarshalIndent(request, "", " "); err != nil { if raw, err := xml.MarshalIndent(request, "", " "); err != nil {
fmt.Println(err) fmt.Println(err)

53
omaha/system.go Normal file
View 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
}
}