@@ -5,6 +5,7 @@ package fargo
55import (
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.
126132func (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+ }
0 commit comments