Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JSON support: #6

Merged
merged 3 commits into from
Jun 18, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (e *EurekaConnection) UpdateApp(app *Application) {
go func() {
for {
log.Notice("Updating app %s", app.Name)
err := e.readAppInto(app.Name, app)
err := e.readAppInto(app)
if err != nil {
log.Error("Failure updating %s in goroutine", app.Name)
}
Expand Down
7 changes: 7 additions & 0 deletions log.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,10 @@ import (
)

var log = logging.MustGetLogger("fargo")
var metadataLog = logging.MustGetLogger("fargo.metadata")
var marshalLog = logging.MustGetLogger("fargo.marshal")

func init() {
logging.SetLevel(logging.WARNING, "fargo.metadata")
logging.SetLevel(logging.WARNING, "fargo.marshal")
}
109 changes: 109 additions & 0 deletions marshal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package fargo
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where I put the custom JSON unmarshalling


// MIT Licensed (see README.md) - Copyright (c) 2013 Hudl <@Hudl>

import (
"encoding/json"
"strconv"
)

// Temporary structs used for GetAppsResponse unmarshalling
type getAppsResponseArray GetAppsResponse
type getAppsResponseSingle struct {
Application *Application `json:"application"`
AppsHashcode string `json:"apps__hashcode"`
VersionsDelta int `json:"versions__delta"`
}

// UnmarshalJSON is a custom JSON unmarshaler for GetAppsResponse to deal with
// sometimes non-wrapped Application arrays when there is only a single Application item.
func (r *GetAppsResponse) UnmarshalJSON(b []byte) error {
marshalLog.Debug("GetAppsResponse.UnmarshalJSON b:%s\n", string(b))
var err error

// Normal array case
var ra getAppsResponseArray
if err = json.Unmarshal(b, &ra); err == nil {
marshalLog.Debug("GetAppsResponse.UnmarshalJSON ra:%+v\n", ra)
*r = GetAppsResponse(ra)
return nil
}
// Bogus non-wrapped case
var rs getAppsResponseSingle
if err = json.Unmarshal(b, &rs); err == nil {
marshalLog.Debug("GetAppsResponse.UnmarshalJSON rs:%+v\n", rs)
r.Applications = make([]*Application, 1, 1)
r.Applications[0] = rs.Application
r.AppsHashcode = rs.AppsHashcode
r.VersionsDelta = rs.VersionsDelta
return nil
}
return err
}

// Temporary structs used for Application unmarshalling
type applicationArray Application
type applicationSingle struct {
Name string `json:"name"`
Instance *Instance `json:"instance"`
}

// UnmarshalJSON is a custom JSON unmarshaler for Application to deal with
// sometimes non-wrapped Instance array when there is only a single Instance item.
func (a *Application) UnmarshalJSON(b []byte) error {
marshalLog.Debug("Application.UnmarshalJSON b:%s\n", string(b))
var err error

// Normal array case
var aa applicationArray
if err = json.Unmarshal(b, &aa); err == nil {
marshalLog.Debug("Application.UnmarshalJSON aa:%+v\n", aa)
*a = Application(aa)
return nil
}

// Bogus non-wrapped case
var as applicationSingle
if err = json.Unmarshal(b, &as); err == nil {
marshalLog.Debug("Application.UnmarshalJSON as:%+v\n", as)
a.Name = as.Name
a.Instances = make([]*Instance, 1, 1)
a.Instances[0] = as.Instance
return nil
}
return err
}

type instance Instance

// UnmarshalJSON is a custom JSON unmarshaler for Instance to deal with the
// different Port encodings between XML and JSON. Here we copy the values from the JSON
// Port struct into the simple XML int field.
func (i *Instance) UnmarshalJSON(b []byte) error {
var err error
var ii instance
if err = json.Unmarshal(b, &ii); err == nil {
marshalLog.Debug("Instance.UnmarshalJSON ii:%+v\n", ii)
*i = Instance(ii)
i.Port = parsePort(ii.PortJ.Number)
i.SecurePort = parsePort(ii.SecurePortJ.Number)
return nil
}
return err
}

func parsePort(s string) int {
n, err := strconv.Atoi(s)
if err != nil {
log.Warning("Invalid port error: %s", err.Error())
}
return n
}

// UnmarshalJSON is a custom JSON unmarshaler for InstanceMetadata to handle squirreling away
// the raw JSON for later parsing.
func (i *InstanceMetadata) UnmarshalJSON(b []byte) error {
i.Raw = b
// TODO(cq) could actually parse Raw here, and in a parallel UnmarshalXML as well.
return nil
}
31 changes: 22 additions & 9 deletions metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package fargo
// MIT Licensed (see README.md) - Copyright (c) 2013 Hudl <@Hudl>

import (
"encoding/json"
"fmt"
"github.com/clbanning/x2j"
)
Expand All @@ -12,27 +13,39 @@ func (a *Application) ParseAllMetadata() error {
for _, instance := range a.Instances {
err := instance.Metadata.parse()
if err != nil {
log.Error("Failed parsing metadata for Instance=%s of Application=%s: ", instance.HostName, a.Name, err.Error())
log.Error("Failed parsing metadata for Instance=%s of Application=%s: %s",
instance.HostName, a.Name, err.Error())
return err
}
}
return nil
}

func (im *InstanceMetadata) parse() error {
// wrap in a BS xml tag so all metadata tags are pulled
if len(im.Raw) == 0 {
im.parsed = make(map[string]interface{})
log.Debug("len(Metadata)==0. Quitting parsing.")
return nil
}
fullDoc := append(append([]byte("<d>"), im.Raw...), []byte("</d>")...)
parsedDoc, err := x2j.ByteDocToMap(fullDoc, true)
if err != nil {
log.Error("Error unmarshalling: ", err.Error())
return fmt.Errorf("error unmarshalling: ", err.Error())
metadataLog.Debug("InstanceMetadata.parse: %s", im.Raw)

if len(im.Raw) > 0 && im.Raw[0] == '{' {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplistic way of deciding if the raw payload is JSON or XML. We could actually do each of them in their own custom unmarshallers.

// JSON
err := json.Unmarshal(im.Raw, &im.parsed)
if err != nil {
log.Error("Error unmarshalling: %s", err.Error())
return fmt.Errorf("error unmarshalling: %s", err.Error())
}
} else {
// XML: wrap in a BS xml tag so all metadata tags are pulled
fullDoc := append(append([]byte("<d>"), im.Raw...), []byte("</d>")...)
parsedDoc, err := x2j.ByteDocToMap(fullDoc, true)
if err != nil {
log.Error("Error unmarshalling: %s", err.Error())
return fmt.Errorf("error unmarshalling: %s", err.Error())
}
im.parsed = parsedDoc["d"].(map[string]interface{})
}
im.parsed = parsedDoc["d"].(map[string]interface{})
return nil
}

Expand All @@ -44,7 +57,7 @@ func (im *InstanceMetadata) GetMap() map[string]interface{} {
func (im *InstanceMetadata) getItem(key string) (interface{}, bool, error) {
err := im.parse()
if err != nil {
return "", false, fmt.Errorf("parsing error: ", err.Error())
return "", false, fmt.Errorf("parsing error: %s", err.Error())
}
val, present := im.parsed[key]
return val, present, nil
Expand Down
Loading