Skip to content

Commit 9a362c8

Browse files
authored
Merge pull request #44 from seh/allow-other-dc-metadata
Accommodate non-Amazon data center info metadata
2 parents 1fc2cae + bb10d2c commit 9a362c8

File tree

3 files changed

+291
-26
lines changed

3 files changed

+291
-26
lines changed

marshal.go

+158-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package fargo
55
import (
66
"encoding/json"
77
"encoding/xml"
8+
"io"
89
"strconv"
910
)
1011

@@ -122,17 +123,22 @@ func (i *InstanceMetadata) MarshalJSON() ([]byte, error) {
122123
return i.Raw, nil
123124
}
124125

126+
// startLocalName creates a start-tag of an XML element with the given local name and no namespace name.
127+
func startLocalName(local string) xml.StartElement {
128+
return xml.StartElement{Name: xml.Name{Space: "", Local: local}}
129+
}
130+
125131
// MarshalXML is a custom XML marshaler for InstanceMetadata.
126132
func (i InstanceMetadata) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
127133
tokens := []xml.Token{start}
128134

129135
if i.parsed != nil {
130136
for key, value := range i.parsed {
131-
t := xml.StartElement{Name: xml.Name{"", key}}
132-
tokens = append(tokens, t, xml.CharData(value.(string)), xml.EndElement{t.Name})
137+
t := startLocalName(key)
138+
tokens = append(tokens, t, xml.CharData(value.(string)), xml.EndElement{Name: t.Name})
133139
}
134140
}
135-
tokens = append(tokens, xml.EndElement{start.Name})
141+
tokens = append(tokens, xml.EndElement{Name: start.Name})
136142

137143
for _, t := range tokens {
138144
err := e.EncodeToken(t)
@@ -144,3 +150,152 @@ func (i InstanceMetadata) MarshalXML(e *xml.Encoder, start xml.StartElement) err
144150
// flush to ensure tokens are written
145151
return e.Flush()
146152
}
153+
154+
type metadataMap map[string]string
155+
156+
// MarshalXML is a custom XML marshaler for metadataMap, mapping each metadata name/value pair to a
157+
// correspondingly named XML element with the pair's value as character data content.
158+
func (m metadataMap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
159+
if err := e.EncodeToken(start); err != nil {
160+
return err
161+
}
162+
163+
for k, v := range m {
164+
if err := e.EncodeElement(v, startLocalName(k)); err != nil {
165+
return err
166+
}
167+
}
168+
169+
return e.EncodeToken(start.End())
170+
}
171+
172+
// UnmarshalXML is a custom XML unmarshaler for metadataMap, mapping each XML element's name and
173+
// character data content to a corresponding metadata name/value pair.
174+
func (m metadataMap) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
175+
var v string
176+
for {
177+
t, err := d.Token()
178+
if err != nil {
179+
if err == io.EOF {
180+
break
181+
}
182+
return err
183+
}
184+
if k, ok := t.(xml.StartElement); ok {
185+
if err := d.DecodeElement(&v, &k); err != nil {
186+
return err
187+
}
188+
m[k.Name.Local] = v
189+
}
190+
}
191+
return nil
192+
}
193+
194+
func metadataValue(i DataCenterInfo) interface{} {
195+
if i.Name == Amazon {
196+
return i.Metadata
197+
}
198+
return metadataMap(i.AlternateMetadata)
199+
}
200+
201+
var (
202+
startName = startLocalName("name")
203+
startMetadata = startLocalName("metadata")
204+
)
205+
206+
// MarshalXML is a custom XML marshaler for DataCenterInfo, writing either Metadata or AlternateMetadata
207+
// depending on the type of data center indicated by the Name.
208+
func (i DataCenterInfo) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
209+
if err := e.EncodeToken(start); err != nil {
210+
return err
211+
}
212+
213+
if err := e.EncodeElement(i.Name, startName); err != nil {
214+
return err
215+
}
216+
if err := e.EncodeElement(metadataValue(i), startMetadata); err != nil {
217+
return err
218+
}
219+
220+
return e.EncodeToken(start.End())
221+
}
222+
223+
type preliminaryDataCenterInfo struct {
224+
Name string `xml:"name" json:"name"`
225+
Metadata metadataMap `xml:"metadata" json:"metadata"`
226+
}
227+
228+
func bindValue(dst *string, src map[string]string, k string) bool {
229+
if v, ok := src[k]; ok {
230+
*dst = v
231+
return true
232+
}
233+
return false
234+
}
235+
236+
func populateAmazonMetadata(dst *AmazonMetadataType, src map[string]string) {
237+
bindValue(&dst.AmiLaunchIndex, src, "ami-launch-index")
238+
bindValue(&dst.LocalHostname, src, "local-hostname")
239+
bindValue(&dst.AvailabilityZone, src, "availability-zone")
240+
bindValue(&dst.InstanceID, src, "instance-id")
241+
bindValue(&dst.PublicIpv4, src, "public-ipv4")
242+
bindValue(&dst.PublicHostname, src, "public-hostname")
243+
bindValue(&dst.AmiManifestPath, src, "ami-manifest-path")
244+
bindValue(&dst.LocalIpv4, src, "local-ipv4")
245+
bindValue(&dst.HostName, src, "hostname")
246+
bindValue(&dst.AmiID, src, "ami-id")
247+
bindValue(&dst.InstanceType, src, "instance-type")
248+
}
249+
250+
func adaptDataCenterInfo(dst *DataCenterInfo, src preliminaryDataCenterInfo) {
251+
dst.Name = src.Name
252+
if src.Name == Amazon {
253+
populateAmazonMetadata(&dst.Metadata, src.Metadata)
254+
} else {
255+
dst.AlternateMetadata = src.Metadata
256+
}
257+
}
258+
259+
// UnmarshalXML is a custom XML unmarshaler for DataCenterInfo, populating either Metadata or AlternateMetadata
260+
// depending on the type of data center indicated by the Name.
261+
func (i *DataCenterInfo) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
262+
p := preliminaryDataCenterInfo{
263+
Metadata: make(map[string]string, 11),
264+
}
265+
if err := d.DecodeElement(&p, &start); err != nil {
266+
return err
267+
}
268+
adaptDataCenterInfo(i, p)
269+
return nil
270+
}
271+
272+
// MarshalJSON is a custom JSON marshaler for DataCenterInfo, writing either Metadata or AlternateMetadata
273+
// depending on the type of data center indicated by the Name.
274+
func (i DataCenterInfo) MarshalJSON() ([]byte, error) {
275+
type named struct {
276+
Name string `json:"name"`
277+
}
278+
if i.Name == Amazon {
279+
return json.Marshal(struct {
280+
named
281+
Metadata AmazonMetadataType `json:"metadata"`
282+
}{named{i.Name}, i.Metadata})
283+
}
284+
return json.Marshal(struct {
285+
named
286+
Metadata map[string]string `json:"metadata"`
287+
}{named{i.Name}, i.AlternateMetadata})
288+
}
289+
290+
// UnmarshalJSON is a custom JSON unmarshaler for DataCenterInfo, populating either Metadata or AlternateMetadata
291+
// depending on the type of data center indicated by the Name.
292+
func (i *DataCenterInfo) UnmarshalJSON(b []byte) error {
293+
p := preliminaryDataCenterInfo{
294+
Metadata: make(map[string]string, 11),
295+
}
296+
if err := json.Unmarshal(b, &p); err != nil {
297+
return err
298+
}
299+
adaptDataCenterInfo(i, p)
300+
return nil
301+
}

struct.go

+26-18
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import (
66
"time"
77
)
88

9-
// EurekaUrlSlugs is a map of resource names -> eureka URLs
9+
// EurekaUrlSlugs is a map of resource names->Eureka URLs.
1010
var EurekaURLSlugs = map[string]string{
1111
"Apps": "apps",
1212
"Instances": "instances",
1313
}
1414

15-
// EurekaConnection is the settings required to make eureka requests
15+
// EurekaConnection is the settings required to make Eureka requests.
1616
type EurekaConnection struct {
1717
ServiceUrls []string
1818
ServicePort int
@@ -26,30 +26,30 @@ type EurekaConnection struct {
2626
UseJson bool
2727
}
2828

29-
// GetAppsResponseJson lets us deserialize the eureka/v2/apps response JSON--a wrapped GetAppsResponse
29+
// GetAppsResponseJson lets us deserialize the eureka/v2/apps response JSONa wrapped GetAppsResponse.
3030
type GetAppsResponseJson struct {
3131
Response *GetAppsResponse `json:"applications"`
3232
}
3333

34-
// GetAppsResponse lets us deserialize the eureka/v2/apps response XML
34+
// GetAppsResponse lets us deserialize the eureka/v2/apps response XML.
3535
type GetAppsResponse struct {
3636
Applications []*Application `xml:"application" json:"application"`
3737
AppsHashcode string `xml:"apps__hashcode" json:"apps__hashcode"`
3838
VersionsDelta int `xml:"versions__delta" json:"versions__delta"`
3939
}
4040

41-
// Application deserializeable from Eureka JSON
41+
// Application deserializeable from Eureka JSON.
4242
type GetAppResponseJson struct {
4343
Application Application `json:"application"`
4444
}
4545

46-
// Application deserializeable from Eureka XML
46+
// Application deserializeable from Eureka XML.
4747
type Application struct {
4848
Name string `xml:"name" json:"name"`
4949
Instances []*Instance `xml:"instance" json:"instance"`
5050
}
5151

52-
// StatusType is an enum of the different statuses allowed by Eureka
52+
// StatusType is an enum of the different statuses allowed by Eureka.
5353
type StatusType string
5454

5555
// Supported statuses
@@ -67,12 +67,12 @@ const (
6767
MyOwn = "MyOwn"
6868
)
6969

70-
// RegisterInstanceJson lets us serialize the eureka/v2/apps/<ins> request JSON--a wrapped Instance
70+
// RegisterInstanceJson lets us serialize the eureka/v2/apps/<ins> request JSONa wrapped Instance.
7171
type RegisterInstanceJson struct {
7272
Instance *Instance `json:"instance"`
7373
}
7474

75-
// Instance [de]serializeable [to|from] Eureka XML
75+
// Instance [de]serializeable [to|from] Eureka XML.
7676
type Instance struct {
7777
XMLName struct{} `xml:"instance" json:"-"`
7878
HostName string `xml:"hostName" json:"hostName"`
@@ -102,21 +102,22 @@ type Instance struct {
102102
UniqueID func(i Instance) string `xml:"-" json:"-"`
103103
}
104104

105-
// Port struct used for JSON [un]marshaling only
106-
// looks like: "port":{"@enabled":"true", "$":"7101"},
105+
// Port struct used for JSON [un]marshaling only.
106+
// An example:
107+
// "port":{"@enabled":"true", "$":"7101"}
107108
type Port struct {
108109
Number string `json:"$"`
109110
Enabled string `json:"@enabled"`
110111
}
111112

112-
// InstanceMetadata represents the eureka metadata, which is arbitrary XML. See
113-
// metadata.go for more info.
113+
// InstanceMetadata represents the eureka metadata, which is arbitrary XML.
114+
// See metadata.go for more info.
114115
type InstanceMetadata struct {
115116
Raw []byte `xml:",innerxml" json:"-"`
116117
parsed map[string]interface{}
117118
}
118119

119-
// AmazonMetadataType is information about AZ's, AMI's, and the AWS instance
120+
// AmazonMetadataType is information about AZ's, AMI's, and the AWS instance.
120121
// <xsd:complexType name="amazonMetdataType">
121122
// from http://docs.amazonwebservices.com/AWSEC2/latest/DeveloperGuide/index.html?AESDG-chapter-instancedata.html
122123
type AmazonMetadataType struct {
@@ -133,13 +134,20 @@ type AmazonMetadataType struct {
133134
InstanceType string `xml:"instance-type" json:"instance-type"`
134135
}
135136

136-
// DataCenterInfo is only really useful when running in AWS.
137+
// DataCenterInfo indicates which type of data center hosts this instance
138+
// and conveys details about the instance's environment.
137139
type DataCenterInfo struct {
138-
Name string `xml:"name" json:"name"`
139-
Metadata AmazonMetadataType `xml:"metadata" json:"metadata"`
140+
// Name indicates which type of data center hosts this instance.
141+
Name string
142+
// Metadata provides details specific to an Amazon data center,
143+
// populated and honored when the Name field's value is "Amazon".
144+
Metadata AmazonMetadataType
145+
// AlternateMetadata provides details specific to a data center other than Amazon,
146+
// populated and honored when the Name field's value is not "Amazon".
147+
AlternateMetadata map[string]string
140148
}
141149

142-
// LeaseInfo tells us about the renewal from Eureka, including how old it is
150+
// LeaseInfo tells us about the renewal from Eureka, including how old it is.
143151
type LeaseInfo struct {
144152
RenewalIntervalInSecs int32 `xml:"renewalIntervalInSecs" json:"renewalIntervalInSecs"`
145153
DurationInSecs int32 `xml:"durationInSecs" json:"durationInSecs"`

0 commit comments

Comments
 (0)