Merge lp:~rvb/gwacl/extract-imageos into lp:gwacl

Proposed by Raphaël Badin
Status: Merged
Approved by: Raphaël Badin
Approved revision: 102
Merged at revision: 105
Proposed branch: lp:~rvb/gwacl/extract-imageos
Merge into: lp:gwacl
Diff against target: 364 lines (+229/-16)
6 files modified
example/management/run.go (+14/-3)
managementapi.go (+2/-2)
managementapi_test.go (+5/-5)
x509dispatcher.go (+1/-1)
xmlobjects.go (+78/-5)
xmlobjects_test.go (+129/-0)
To merge this branch: bzr merge lp:~rvb/gwacl/extract-imageos
Reviewer Review Type Date Requested Status
Julian Edwards (community) Approve
Review via email: mp+161171@code.launchpad.net

Commit message

Add method to extract the OS image for a particular series.

Description of the change

The main change in this branch is to add the method GetLatestUbuntuImage() used to extract, from the list of available OS Images, the one corresponding to a particular series and available in a particular location.

Note that I bumped the api version to get the new variables on the OS Images objects (in particular, PublishedDate was not available in the old version).

To post a comment you must log in.
Revision history for this message
Julian Edwards (julian-edwards) wrote :
Download full text (5.2 KiB)

Nice branch! I have a few niggles so it's needs-fixing for now, but nothing big.

95 // baseAPIVersion is the default Azure API version to use.
96 -const baseAPIVersion = "2012-03-01"
97 +const baseAPIVersion = "2013-03-01"

As I said on the review of jtv's branch, I think it's a bad idea to have a blanket default API version if different API calls can have different versions (there's no good default for that). If this is not the case for the management API then ignore this, otherwise I strongly urge you to think about placing explicit versions in each part of the code that does an Azure API call.

102 === modified file 'xmlobjects.go'

I can see why you did it, but I don't think the stuff you added here should go in this file. It was intended to be just a file of struct definitions; I think it would be better to make your changes in something like 'osimages.go'. In fact I think it's time to consider splitting up that xmlobjects.go file, it's getting rather unwieldy. But, later.

146 +// Less sort the images according to their 'PublishedDate'. Most recently published
147 +// first.

This description really confused me. I first thought s/sort/sorts/ but then having looked at what the function is actually doing, I find the claim that it's sorting even more confusing! I think this is what you need, including a change of function name:

// IsNewer returns true if the image at index i is newer than the one at index j, comparing by PublishedDate.
func (images *Images) IsNewer(i, j int) bool {

NB - I just read further down the code and realised to my horror that this name is part of the sort.Sort() contract isn't it? Sigh :/ In that case:

// Less returns true if the image at index i is newer than the one at index j, comparing by PublishedDate.
// This function is used by sort.Sort().
func (images *Images) Less(i, j int) bool {

148 +func (images *Images) Less(i, j int) bool {
149 + dateStringI := images.Images[i].PublishedDate
150 + dateI, err := time.Parse(time.RFC3339, dateStringI)
151 + if err != nil {
152 + panic(fmt.Errorf("Failed to parse image's 'PublishedDate': %s", dateStringI))
153 + }
154 + dateStringJ := images.Images[j].PublishedDate
155 + dateJ, err := time.Parse(time.RFC3339, dateStringJ)
156 + if err != nil {
157 + panic(fmt.Errorf("Failed to parse image's 'PublishedDate': %s", dateStringJ))

There is no catch for the panic statements here, and we should not leak the panic out of the library. Please return an error instead. You can use the exact same string, that's fine but I'd also include any context info about the image to help identify which one cannot be parsed.

164 +func (images *Images) GetLatestUbuntuImage(release string, location string) (*OSImage, error) {
165 + version, present := releaseVersions[release]
166 + if !present {
167 + return nil, fmt.Errorf("Unsupported release: %s", release)
168 + }

It's bad form to refer to released Ubuntu series by the development codename. The releaseVersions map is unnecessary, we should just expect people to pass "12.04", "12.10" etc as those are the official release names.

178 + sort.Sort(&matchingImages)
179 + i...

Read more...

review: Needs Fixing
Revision history for this message
Julian Edwards (julian-edwards) wrote :

I forgot to mention, thank you for adding context to errors instead of blindly propagating them up the stack. This is something that we should always be doing so that errors can be traced to source, and not just a "wtf" moment.

Revision history for this message
Raphaël Badin (rvb) wrote :
Download full text (6.0 KiB)

Thanks for the review!

> Nice branch! I have a few niggles so it's needs-fixing for now, but nothing
> big.
>
> 95 // baseAPIVersion is the default Azure API version to use.
> 96 -const baseAPIVersion = "2012-03-01"
> 97 +const baseAPIVersion = "2013-03-01"
>
> As I said on the review of jtv's branch, I think it's a bad idea to have a
> blanket default API version if different API calls can have different versions
> (there's no good default for that). If this is not the case for the
> management API then ignore this, otherwise I strongly urge you to think about
> placing explicit versions in each part of the code that does an Azure API
> call.

You're right, let's do this in another branch this.

> NB - I just read further down the code and realised to my horror that this
> name is part of the sort.Sort() contract isn't it? Sigh :/ In that case:
>
> // Less returns true if the image at index i is newer than the one at index j,
> comparing by PublishedDate.
> // This function is used by sort.Sort().
> func (images *Images) Less(i, j int) bool {

This is indeed needed by the sort.Sort() contract. Doc fixed.

> 148 +func (images *Images) Less(i, j int) bool {
> 149 + dateStringI := images.Images[i].PublishedDate
> 150 + dateI, err := time.Parse(time.RFC3339, dateStringI)
> 151 + if err != nil {
> 152 + panic(fmt.Errorf("Failed to parse image's 'PublishedDate':
> %s", dateStringI))
> 153 + }
> 154 + dateStringJ := images.Images[j].PublishedDate
> 155 + dateJ, err := time.Parse(time.RFC3339, dateStringJ)
> 156 + if err != nil {
> 157 + panic(fmt.Errorf("Failed to parse image's 'PublishedDate':
> %s", dateStringJ))
>
> There is no catch for the panic statements here, and we should not leak the
> panic out of the library. Please return an error instead. You can use the
> exact same string, that's fine but I'd also include any context info about the
> image to help identify which one cannot be parsed.

I cannot do that as Less() is part of the srot.Sort() contract so I've changed the caller to recover from panic errors "raised" from Less().

> 164 +func (images *Images) GetLatestUbuntuImage(release string, location
> string) (*OSImage, error) {
> 165 + version, present := releaseVersions[release]
> 166 + if !present {
> 167 + return nil, fmt.Errorf("Unsupported release: %s", release)
> 168 + }
>
> It's bad form to refer to released Ubuntu series by the development codename.
> The releaseVersions map is unnecessary, we should just expect people to pass
> "12.04", "12.10" etc as those are the official release names.

I'm really not sure about this:
- distro-info, which is the official source of release names, returns the codename as default. There is an option to get the full name but really, the codename seems to be the "canonical" name for a series. (http://paste.ubuntu.com/5652819/)
- we've always used codenames in MAAS
- Andres confirms that codenames are *the* "canonical" name for series

>
> 178 + sort.Sort(&matchingImages)
> 179 + if matchingImages.Len() == 0 {
> 180 + return nil, ...

Read more...

lp:~rvb/gwacl/extract-imageos updated
101. By Raphaël Badin

Review fixes.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

 review: approve

Hi,

I still want the release name thing fixed (see below), but nothing
else is of concern so please feel free to land it once that's addressed.

On 11/05/13 09:47, Raphaël Badin wrote:
> Thanks for the review!

My pleasure.

> You're right, let's do this in another branch this.

No problem.

> I cannot do that as Less() is part of the srot.Sort() contract so
> I've changed the caller to recover from panic errors "raised" from
> Less().

Ah jeez, ok! So Go does have exceptions after all :)

>> 164 +func (images *Images) GetLatestUbuntuImage(release
>> string, location string) (*OSImage, error) { 165 +
>> version, present := releaseVersions[release] 166 + if
>> !present { 167 + return nil, fmt.Errorf("Unsupported
>> release: %s", release) 168 + }
>>
>> It's bad form to refer to released Ubuntu series by the
>> development codename. The releaseVersions map is unnecessary, we
>> should just expect people to pass "12.04", "12.10" etc as those
>> are the official release names.
>
> I'm really not sure about this: - distro-info, which is the
> official source of release names, returns the codename as default.
> There is an option to get the full name but really, the codename
> seems to be the "canonical" name for a series.
> (http://paste.ubuntu.com/5652819/) - we've always used codenames in
> MAAS

distro-info is intended for use where things require the code name,
such as in a sources.list.

> - Andres confirms that codenames are *the* "canonical" name for
> series

Andres is wrong then. They are used internally, yes, but not
externally. Externally, the release name is always the NN.NN form.

Before an Ubuntu series is released it is indeed always called by its
code name. However, once released it is called by its release name,
such as "12.04". This is reflected in how the Launchpad UI works in
the Soyuz pages, of which I have plenty of experience :)

I am happy to compromise and make the function take either form, but
we should never rely entirely on internal release names, and if MAAS
is doing that we need to fix MAAS as well.

>
>>
>> 178 + sort.Sort(&matchingImages) 179 + if
>> matchingImages.Len() == 0 { 180 + return nil,
>> fmt.Errorf("No matching images found") 181 + }
>>
>> You can probably switch these two statements. Also - Azure
>> really returns images in an indeterminate order? :/
>
> It does not say, so I figured: better safe than sorry.

Wise :)

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.12 (GNU/Linux)
Comment: Using GnuPG with Thunderbird - http://www.enigmail.net/

iEYEARECAAYFAlGQP7QACgkQWhGlTF8G/HdVlQCdHp7AyUzyUsoDTsCD6MvxuD3V
IEoAnAoGk/kGYNcxcwhjw9Kk3aBJKscv
=C7Yk
-----END PGP SIGNATURE-----

review: Approve
lp:~rvb/gwacl/extract-imageos updated
102. By Raphaël Badin

Review fixes (2).

Revision history for this message
Raphaël Badin (rvb) wrote :

> I still want the release name thing fixed (see below), but nothing
> else is of concern so please feel free to land it once that's addressed.

All right. I've fixed the release name thing as advised but I still think we need to have a look at that because MAAS and Juju use the codename everywhere (I /think/).

Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Wednesday 15 May 2013 07:43:26 you wrote:
> > I still want the release name thing fixed (see below), but nothing
> > else is of concern so please feel free to land it once that's addressed.
>
> All right. I've fixed the release name thing as advised but I still think
> we need to have a look at that because MAAS and Juju use the codename
> everywhere (I /think/).

I agree.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'example/management/run.go'
2--- example/management/run.go 2013-04-25 15:06:21 +0000
3+++ example/management/run.go 2013-05-15 07:42:26 +0000
4@@ -85,11 +85,23 @@
5 }
6
7 func ExerciseHostedServicesAPI(api *gwacl.ManagementAPI) {
8+ location := "East US"
9+ series := "precise"
10+
11+ fmt.Printf("Getting OS Image for series '%s' and location '%s'...\n", series, location)
12+ images, err := api.ListOSImages()
13+ checkError(err)
14+ preciseImage, err := images.GetLatestUbuntuImage(series, location)
15+ checkError(err)
16+ sourceImageName := preciseImage.Name
17+ fmt.Printf("Got image named '%s'\n", sourceImageName)
18+ fmt.Println("Done getting OS Image\n")
19+
20 // A hosted-service name must be between 3 and 24 characters long.
21 hostServiceName := makeRandomIdentifier("gwacl", 24)
22 fmt.Printf("Creating a hosted service: '%s'...\n", hostServiceName)
23- createHostedService := gwacl.NewCreateHostedServiceWithLocation(hostServiceName, "testLabel", "East US")
24- err := api.AddHostedService(createHostedService)
25+ createHostedService := gwacl.NewCreateHostedServiceWithLocation(hostServiceName, "testLabel", location)
26+ err = api.AddHostedService(createHostedService)
27 checkError(err)
28 fmt.Println("Done creating a hosted service\n")
29
30@@ -120,7 +132,6 @@
31 fmt.Println("Done requesting storage account\n")
32
33 mediaLink := fmt.Sprintf("http://%s.blob.core.windows.net/vhds/%s.vhd", storageAccount, vhdName)
34- sourceImageName := "b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-12_04_2-LTS-amd64-server-20130325-en-us-30GB"
35 diskName := makeRandomIdentifier("gwacldisk", 16)
36 diskLabel := makeRandomIdentifier("gwacl", 64)
37 OSVirtualHardDisk := gwacl.NewOSVirtualHardDisk("", diskLabel, diskName, mediaLink, sourceImageName)
38
39=== modified file 'managementapi.go'
40--- managementapi.go 2013-04-16 13:29:25 +0000
41+++ managementapi.go 2013-05-15 07:42:26 +0000
42@@ -96,14 +96,14 @@
43 // from the Azure management API.
44 // Images are returned in the order in which Azure lists them.
45 // http://msdn.microsoft.com/en-us/library/windowsazure/jj157191.aspx
46-func (api *ManagementAPI) ListOSImages() ([]OSImage, error) {
47+func (api *ManagementAPI) ListOSImages() (*Images, error) {
48 response, err := api.session.get("services/images")
49 if err != nil {
50 return nil, err
51 }
52 images := Images{}
53 err = xml.Unmarshal(response.Body, &images)
54- return images.Images, err
55+ return &images, err
56 }
57
58 // ListHostedServiceDescriptors loads a list of HostedServiceDescriptor objects from the
59
60=== modified file 'managementapi_test.go'
61--- managementapi_test.go 2013-04-16 13:09:54 +0000
62+++ managementapi_test.go 2013-05-15 07:42:26 +0000
63@@ -318,8 +318,8 @@
64 listing, err := api.ListOSImages()
65 c.Assert(err, IsNil)
66
67- c.Assert(len(listing), Equals, 1)
68- c.Check(listing[0], DeepEquals, expectedImage)
69+ c.Assert(len(listing.Images), Equals, 1)
70+ c.Check(listing.Images[0], DeepEquals, expectedImage)
71 }
72
73 func (suite *managementAPISuite) TestListOSImagesPreservesOrdering(c *C) {
74@@ -342,10 +342,10 @@
75 listing, err := api.ListOSImages()
76 c.Assert(err, IsNil)
77
78- c.Assert(len(listing), Equals, 3)
79+ c.Assert(len(listing.Images), Equals, 3)
80 receivedNames := make([]string, 3)
81- for index := range listing {
82- receivedNames[index] = listing[index].Name
83+ for index := range listing.Images {
84+ receivedNames[index] = listing.Images[index].Name
85 }
86 c.Check(receivedNames, DeepEquals, imageNames)
87 }
88
89=== modified file 'x509dispatcher.go'
90--- x509dispatcher.go 2013-04-22 16:20:00 +0000
91+++ x509dispatcher.go 2013-05-15 07:42:26 +0000
92@@ -26,7 +26,7 @@
93 }
94
95 // baseAPIVersion is the default Azure API version to use.
96-const baseAPIVersion = "2012-03-01"
97+const baseAPIVersion = "2013-03-01"
98
99 // newX509RequestGET initializes an x509Request for a GET. You may still need
100 // to set further values.
101
102=== modified file 'xmlobjects.go'
103--- xmlobjects.go 2013-04-29 07:19:57 +0000
104+++ xmlobjects.go 2013-05-15 07:42:26 +0000
105@@ -3,9 +3,15 @@
106
107 package gwacl
108
109-import "encoding/xml"
110-import "encoding/base64"
111-import "fmt"
112+import (
113+ "encoding/base64"
114+ "encoding/xml"
115+ "fmt"
116+ "regexp"
117+ "sort"
118+ "strings"
119+ "time"
120+)
121
122 // It's impossible to have any kind of common method inherited into all the
123 // various serializable objects because the receiver of the method is the
124@@ -86,6 +92,63 @@
125 return xml.Unmarshal(data, i)
126 }
127
128+var canonicalPublisherName = "Canonical"
129+var imageFamilyFormatRegexp = "^Ubuntu Server %s.*$"
130+
131+func (images *Images) Len() int {
132+ return len(images.Images)
133+}
134+
135+func (images *Images) Swap(i, j int) {
136+ images.Images[i], images.Images[j] = images.Images[j], images.Images[i]
137+}
138+
139+// Less returns true if the image at index i is newer than the one at index j, comparing by
140+// PublishedDate.
141+// This function is used by sort.Sort().
142+func (images *Images) Less(i, j int) bool {
143+ // We need to implement the sort interface so Less cannot return an error. We panic if
144+ // one of the dates cannot be parse and the calling method will recover this.
145+ dateStringI := images.Images[i].PublishedDate
146+ dateI, err := time.Parse(time.RFC3339, dateStringI)
147+ if err != nil {
148+ panic(fmt.Errorf("Failed to parse image's 'PublishedDate': %s", dateStringI))
149+ }
150+ dateStringJ := images.Images[j].PublishedDate
151+ dateJ, err := time.Parse(time.RFC3339, dateStringJ)
152+ if err != nil {
153+ panic(fmt.Errorf("Failed to parse image's 'PublishedDate': %s", dateStringJ))
154+ }
155+ return dateI.After(dateJ)
156+}
157+
158+// GetLatestUbuntuImage returns the most recent available OSImage, for the given release name
159+// and location.
160+func (images *Images) GetLatestUbuntuImage(releaseName string, location string) (image *OSImage, err error) {
161+ // The Less method defined above can panic if one of the published dates cannot be parsed,
162+ // this code recovers from that and transforms that into an error.
163+ defer func() {
164+ if recoveredErr := recover(); recoveredErr != nil {
165+ image = nil
166+ err = recoveredErr.(error)
167+ }
168+ }()
169+ matcherRegexp := regexp.MustCompile(fmt.Sprintf(imageFamilyFormatRegexp, releaseName))
170+ matchingImages := Images{}
171+ for _, image := range images.Images {
172+ if image.PublisherName == canonicalPublisherName &&
173+ matcherRegexp.MatchString(image.ImageFamily) &&
174+ image.hasLocation(location) {
175+ matchingImages.Images = append(matchingImages.Images, image)
176+ }
177+ }
178+ if matchingImages.Len() == 0 {
179+ return nil, fmt.Errorf("No matching images found")
180+ }
181+ sort.Sort(&matchingImages)
182+ return &matchingImages.Images[0], nil
183+}
184+
185 //
186 // OSImage bits
187 //
188@@ -96,7 +159,7 @@
189 AffinityGroup string `xml:"AffinityGroup,omitempty"`
190 Category string `xml:"Category"`
191 Label string `xml:"Label"`
192- Location string `xml:"Location,omitempty"`
193+ Location string `xml:"Location"`
194 LogicalSizeInGB float32 `xml:"LogicalSizeInGB"`
195 MediaLink string `xml:"MediaLink"`
196 Name string `xml:"Name"`
197@@ -105,12 +168,22 @@
198 Description string `xml:"Description,omitempty"`
199 ImageFamily string `xml:"ImageFamily,omitempty"`
200 PublishedDate string `xml:"PublishedDate,omitempty"`
201- IsPremium string `xml:"IsPremium,omitempty"` // Reserved for future use. Seriously. In XML.
202+ IsPremium string `xml:"IsPremium,omitempty"`
203 PrivacyURI string `xml:"PrivacyUri,omitempty"`
204 RecommendedVMSize string `xml:"RecommendedVMSize,omitempty"`
205 PublisherName string `xml:"PublisherName"`
206 }
207
208+func (image *OSImage) hasLocation(location string) bool {
209+ locations := strings.Split(image.Location, ";")
210+ for _, loc := range locations {
211+ if loc == location {
212+ return true
213+ }
214+ }
215+ return false
216+}
217+
218 func (i *OSImage) Deserialize(data []byte) error {
219 return xml.Unmarshal(data, i)
220 }
221
222=== modified file 'xmlobjects_test.go'
223--- xmlobjects_test.go 2013-04-29 07:19:57 +0000
224+++ xmlobjects_test.go 2013-05-15 07:42:26 +0000
225@@ -9,6 +9,7 @@
226 "fmt"
227 . "launchpad.net/gocheck"
228 "launchpad.net/gwacl/dedent"
229+ "sort"
230 )
231
232 type xmlSuite struct{}
233@@ -744,3 +745,131 @@
234 c.Assert(err, IsNil)
235 c.Check(string(xml), Equals, expectedXML)
236 }
237+
238+func (suite *xmlSuite) TestOSImageHasLocation(c *C) {
239+ image := &OSImage{
240+ Location: "East Asia;Southeast Asia;North Europe;West Europe;East US;West US",
241+ }
242+
243+ var testValues = []struct {
244+ location string
245+ expectedResult bool
246+ }{
247+ {"East Asia", true},
248+ {"West US", true},
249+ {"Unknown location", false},
250+ }
251+ for _, test := range testValues {
252+ c.Check(image.hasLocation(test.location), Equals, test.expectedResult)
253+ }
254+}
255+
256+func (suite *xmlSuite) TestSortImages(c *C) {
257+ input := `
258+<?xml version="1.0"?>
259+<Images xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
260+ <OSImage>
261+ <Label>Label 1</Label>
262+ <PublishedDate>2012-04-25T00:00:00Z</PublishedDate>
263+ </OSImage>
264+ <OSImage>
265+ <Label>Label 2</Label>
266+ <PublishedDate>2013-02-15T00:00:00Z</PublishedDate>
267+ </OSImage>
268+ <OSImage>
269+ <Label>Label 3</Label>
270+ <PublishedDate>2013-04-13T00:00:00Z</PublishedDate>
271+ </OSImage>
272+ <OSImage>
273+ <Label>Label 4</Label>
274+ <PublishedDate>2013-03-15T00:00:00Z</PublishedDate>
275+ </OSImage>
276+</Images>`
277+
278+ images := &Images{}
279+ err := images.Deserialize([]byte(input))
280+ c.Assert(err, IsNil)
281+
282+ sort.Sort(images)
283+ labels := []string{
284+ (*images).Images[0].Label,
285+ (*images).Images[1].Label,
286+ (*images).Images[2].Label,
287+ (*images).Images[3].Label,
288+ }
289+ c.Check(labels, DeepEquals, []string{"Label 3", "Label 4", "Label 2", "Label 1"})
290+}
291+
292+func (suite *xmlSuite) TestGetLatestUbuntuImage(c *C) {
293+ // This is real-world XML input.
294+ input := `
295+<?xml version="1.0"?>
296+<Images xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
297+ <OSImage>
298+ <Category>Canonical</Category>
299+ <Label>Ubuntu Server 12.04.2 LTS</Label>
300+ <Location>East Asia;Southeast Asia;North Europe;West Europe;East US;West US</Location>
301+ <LogicalSizeInGB>30</LogicalSizeInGB>
302+ <Name>b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-12_04_2-LTS-amd64-server-20130415-en-us-30GB</Name>
303+ <OS>Linux</OS>
304+ <ImageFamily>Ubuntu Server 12.04 LTS</ImageFamily>
305+ <PublishedDate>2013-04-15T00:00:00Z</PublishedDate>
306+ <IsPremium>false</IsPremium>
307+ <PublisherName>Canonical</PublisherName>
308+ </OSImage>
309+ <OSImage>
310+ <Category>Canonical</Category>
311+ <Label>Ubuntu Server 12.10</Label>
312+ <Location>East Asia;Southeast Asia;North Europe;West Europe;East US;West US</Location>
313+ <LogicalSizeInGB>30</LogicalSizeInGB>
314+ <Name>b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-12_10-amd64-server-20130414-en-us-30GB</Name>
315+ <OS>Linux</OS>
316+ <ImageFamily>Ubuntu 12.10</ImageFamily>
317+ <PublishedDate>2013-04-15T00:00:00Z</PublishedDate>
318+ <PublisherName>Canonical</PublisherName>
319+ </OSImage>
320+ <OSImage>
321+ <Category>Canonical</Category>
322+ <Label>Ubuntu Server 13.04</Label>
323+ <Location>East Asia;Southeast Asia;North Europe;West Europe;East US;West US</Location>
324+ <LogicalSizeInGB>30</LogicalSizeInGB>
325+ <Name>b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-13_04-amd64-server-20130423-en-us-30GB</Name>
326+ <OS>Linux</OS>
327+ <ImageFamily>Ubuntu Server 13.04</ImageFamily>
328+ <PublishedDate>2013-04-25T00:00:00Z</PublishedDate>
329+ <PublisherName>Canonical</PublisherName>
330+ </OSImage>
331+ <OSImage>
332+ <Category>Canonical</Category>
333+ <Label>Ubuntu Server 13.04 bogus publisher name</Label>
334+ <Location>East Asia;Southeast Asia;North Europe;West Europe;East US;West US</Location>
335+ <LogicalSizeInGB>30</LogicalSizeInGB>
336+ <Name>b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-13_04-amd64-server-20130423-en-us-30GB</Name>
337+ <OS>Linux</OS>
338+ <PublishedDate>2013-05-25T00:00:00Z</PublishedDate>
339+ <ImageFamily>Ubuntu Server 13.04</ImageFamily>
340+ <PublisherName>Bogus publisher name</PublisherName>
341+ </OSImage>
342+</Images>`
343+ images := &Images{}
344+ err := images.Deserialize([]byte(input))
345+ c.Assert(err, IsNil)
346+
347+ var testValues = []struct {
348+ releaseName string
349+ location string
350+ expectedError error
351+ expectedLabel string
352+ }{
353+ {"13.04", "West US", nil, "Ubuntu Server 13.04"},
354+ {"12.04", "West US", nil, "Ubuntu Server 12.04.2 LTS"},
355+ {"bogus-name", "Unknown location", fmt.Errorf("No matching images found"), ""},
356+ }
357+ for _, test := range testValues {
358+ image, err := images.GetLatestUbuntuImage(test.releaseName, test.location)
359+ c.Check(err, DeepEquals, test.expectedError)
360+ if image != nil {
361+ c.Check(image.Label, Equals, test.expectedLabel)
362+ }
363+ }
364+}

Subscribers

People subscribed via source and target branches

to all changes: