Merge lp:~rvb/gwacl/extract-imageos into lp:gwacl
- extract-imageos
- Merge into trunk
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 |
Related bugs: |
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 GetLatestUbuntu
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).
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.
Raphaël Badin (rvb) wrote : | # |
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.
> 150 + dateI, err := time.Parse(
> 151 + if err != nil {
> 152 + panic(fmt.
> %s", dateStringI))
> 153 + }
> 154 + dateStringJ := images.
> 155 + dateJ, err := time.Parse(
> 156 + if err != nil {
> 157 + panic(fmt.
> %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) GetLatestUbuntu
> string) (*OSImage, error) {
> 165 + version, present := releaseVersions
> 166 + if !present {
> 167 + return nil, fmt.Errorf(
> 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://
- we've always used codenames in MAAS
- Andres confirms that codenames are *the* "canonical" name for series
>
> 178 + sort.Sort(
> 179 + if matchingImages.
> 180 + return nil, ...
- 101. By Raphaël Badin
-
Review fixes.
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) GetLatestUbuntu
>> string, location string) (*OSImage, error) { 165 +
>> version, present := releaseVersions
>> !present { 167 + return nil, fmt.Errorf(
>> 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://
> 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.
>> 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://
iEYEARECAAYFAlG
IEoAnAoGk/
=C7Yk
-----END PGP SIGNATURE-----
- 102. By Raphaël Badin
-
Review fixes (2).
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/).
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
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 | +} |
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 { Images[ i].PublishedDat e time.RFC3339, dateStringI) Errorf( "Failed to parse image's 'PublishedDate': %s", dateStringI)) Images[ j].PublishedDat e time.RFC3339, dateStringJ) Errorf( "Failed to parse image's 'PublishedDate': %s", dateStringJ))
149 + dateStringI := images.
150 + dateI, err := time.Parse(
151 + if err != nil {
152 + panic(fmt.
153 + }
154 + dateStringJ := images.
155 + dateJ, err := time.Parse(
156 + if err != nil {
157 + panic(fmt.
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) GetLatestUbuntu Image(release string, location string) (*OSImage, error) { [release] "Unsupported release: %s", release)
165 + version, present := releaseVersions
166 + if !present {
167 + return nil, fmt.Errorf(
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...