Merge lp:~allenap/gwacl/destroy-everything into lp:gwacl

Proposed by Gavin Panella
Status: Needs review
Proposed branch: lp:~allenap/gwacl/destroy-everything
Merge into: lp:gwacl
Diff against target: 471 lines (+382/-11)
5 files modified
example/destroyer/run.go (+113/-0)
management_base.go (+31/-0)
management_base_test.go (+104/-0)
xmlobjects.go (+36/-1)
xmlobjects_test.go (+98/-10)
To merge this branch: bzr merge lp:~allenap/gwacl/destroy-everything
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+177420@code.launchpad.net

Commit message

Script to destroy most things belonging to a subscription.

Description of the change

I wrote this because I had loads of artifacts in my Azure account that would take me a day to remove, which is more time than it has taken to implement this script and run it.

Along the way I had to implement ListDisks() and ListStorageAccounts(). It also uncovered an error in the XML that's generated in TestStorageServicesUnmarshal(), which is an argument for using the example XML basically as-is from the Azure docs. This actually seems to be what we're doing these days, but it's interesting to note anyway.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

This looks helpful — although please say very clearly at the top of the destroyer program what it does! Otherwise people might expect it to prompt for the thing they want to delete, or something.

The literal XML in tests leads to funny situations, like "true|false" as a Boolean field, but it works for me.

One thing I'm not a fan of is the happy-path-only tests with names like TestFunction. When you push yourself there's usually a corner case you can find that could gainfully be tested. It's a useful habit not just for improving test coverage, but for honing testability skills as well — it trains you to recognize unnecessarily complex code, code that's hard to test exhaustively, unnecessary special cases, and forgotten corner cases.

Revision history for this message
Jeroen T. Vermeulen (jtv) :
review: Approve

Unmerged revisions

211. By Gavin Panella

Remove virtual networks too.

210. By Gavin Panella

Example script to destroy (almost) everything.

209. By Gavin Panella

Whitespace.

208. By Gavin Panella

New method ListDisks().

207. By Gavin Panella

New struct for Disks.

206. By Gavin Panella

New method, ListStorageAccounts.

205. By Gavin Panella

Invalid StorageServices template for test.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added directory 'example/destroyer'
=== added file 'example/destroyer/run.go'
--- example/destroyer/run.go 1970-01-01 00:00:00 +0000
+++ example/destroyer/run.go 2013-07-29 15:50:39 +0000
@@ -0,0 +1,113 @@
1// Copyright 2013 Canonical Ltd. This software is licensed under the
2// GNU Lesser General Public License version 3 (see the file COPYING).
3
4/*
5This is an example on how the Azure Go library can be used to interact with
6the Windows Azure Service.
7Note that this is a provided only as an example and that real code should
8probably do something more sensible with errors than ignoring them or panicking.
9*/
10package main
11
12import (
13 "flag"
14 "fmt"
15 "launchpad.net/gwacl"
16 "os"
17)
18
19var certFile string
20var subscriptionID string
21
22func getParams() error {
23 flag.StringVar(&certFile, "cert", "", "Name of your management certificate file (in PEM format).")
24 flag.StringVar(&subscriptionID, "subscriptionid", "", "Your Azure subscription ID.")
25 flag.Parse()
26 if certFile == "" {
27 return fmt.Errorf("No .pem certificate specified. Use the -cert option.")
28 }
29 if subscriptionID == "" {
30 return fmt.Errorf("No subscription ID specified. Use the -subscriptionid option.")
31 }
32 return nil
33}
34
35func checkError(err error) {
36 if err != nil {
37 panic(err)
38 }
39}
40
41func main() {
42 err := getParams()
43 if err != nil {
44 fmt.Println(err)
45 os.Exit(1)
46 }
47
48 api, err := gwacl.NewManagementAPI(subscriptionID, certFile)
49 checkError(err)
50
51 DestroyEverything(api)
52}
53
54func DestroyEverything(api *gwacl.ManagementAPI) {
55 hostedServices, err := api.ListHostedServices()
56 checkError(err)
57
58 for _, hostedService := range hostedServices {
59 err := api.DestroyHostedService(
60 &gwacl.DestroyHostedServiceRequest{hostedService.ServiceName})
61 checkError(err)
62 }
63
64 disks, err := api.ListDisks()
65 checkError(err)
66
67 // Delete all disks and their storage.
68 for _, disk := range disks {
69 err := api.DeleteDisk(&gwacl.DeleteDiskRequest{
70 DiskName: disk.Name,
71 DeleteBlob: true,
72 })
73 checkError(err)
74 }
75
76 storageAccounts, err := api.ListStorageAccounts()
77 checkError(err)
78
79 for _, storageAccount := range storageAccounts {
80 storageKeys, err := api.GetStorageAccountKeys(storageAccount.ServiceName)
81 checkError(err)
82
83 storageContext := &gwacl.StorageContext{
84 Account: storageAccount.ServiceName,
85 Key: storageKeys.Primary,
86 }
87
88 storageContainers, err := storageContext.ListAllContainers()
89 checkError(err)
90
91 // Delete all blobs in all containers, and the containers themselves.
92 for _, storageContainer := range storageContainers.Containers {
93 err := storageContext.DeleteAllBlobs(
94 &gwacl.DeleteAllBlobsRequest{storageContainer.Name})
95 checkError(err)
96 err = storageContext.DeleteContainer(storageContainer.Name)
97 checkError(err)
98 }
99
100 // Delete the storage account.
101 err = api.DeleteStorageAccount(storageAccount.ServiceName)
102 checkError(err)
103 }
104
105 // Remove virtual networks.
106 networkConfig, err := api.GetNetworkConfiguration()
107 checkError(err)
108 if networkConfig != nil && networkConfig.VirtualNetworkSites != nil {
109 networkConfig.VirtualNetworkSites = &[]gwacl.VirtualNetworkSite{}
110 err := api.SetNetworkConfiguration(networkConfig)
111 checkError(err)
112 }
113}
0114
=== modified file 'management_base.go'
--- management_base.go 2013-07-25 22:02:41 +0000
+++ management_base.go 2013-07-29 15:50:39 +0000
@@ -274,6 +274,23 @@
274 return &deployment, nil274 return &deployment, nil
275}275}
276276
277// ListStorageAccount lists all storage accounts. This is called a storage
278// service in the Azure API, but nomenclature seems to have changed.
279// See http://msdn.microsoft.com/en-us/library/windowsazure/ee460787.aspx
280func (api *ManagementAPI) ListStorageAccounts() ([]StorageService, error) {
281 uri := "services/storageservices"
282 response, err := api.session.get(uri, "2012-03-01")
283 if err != nil {
284 return nil, err
285 }
286 services := &StorageServices{}
287 err = services.Deserialize(response.Body)
288 if err != nil {
289 return nil, err
290 }
291 return services.StorageServices, nil
292}
293
277// AddStorageAccount starts the creation of a storage account. This is294// AddStorageAccount starts the creation of a storage account. This is
278// called a storage service in the Azure API, but nomenclature seems to295// called a storage service in the Azure API, but nomenclature seems to
279// have changed.296// have changed.
@@ -324,6 +341,20 @@
324 return &keys, nil341 return &keys, nil
325}342}
326343
344// ListDisk lists all disks.
345func (api *ManagementAPI) ListDisks() ([]Disk, error) {
346 res, err := api.session.get("services/disks", "2012-03-01")
347 if err != nil {
348 return nil, err
349 }
350 disks := &Disks{}
351 err = disks.Deserialize(res.Body)
352 if err != nil {
353 return nil, err
354 }
355 return disks.Disks, nil
356}
357
327type DeleteDiskRequest struct {358type DeleteDiskRequest struct {
328 DiskName string // Name of the disk to delete.359 DiskName string // Name of the disk to delete.
329 DeleteBlob bool // Whether to delete the associated blob storage.360 DeleteBlob bool // Whether to delete the associated blob storage.
330361
=== modified file 'management_base_test.go'
--- management_base_test.go 2013-07-25 22:02:41 +0000
+++ management_base_test.go 2013-07-29 15:50:39 +0000
@@ -802,6 +802,80 @@
802 c.Check(deployment.Name, Equals, deploymentName)802 c.Check(deployment.Name, Equals, deploymentName)
803}803}
804804
805func (suite *managementBaseAPISuite) TestListStorageAccounts(c *C) {
806 body := dedent.Dedent(`
807 <?xml version="1.0" encoding="utf-8"?>
808 <StorageServices xmlns="http://schemas.microsoft.com/windowsazure">
809 <StorageService>
810 <Url>storage-service-address</Url>
811 <ServiceName>storage-service-name</ServiceName>
812 <StorageServiceProperties>
813 <Description>description</Description>
814 <AffinityGroup>affinity-group</AffinityGroup>
815 <Label>base64-encoded-label</Label>
816 <Status>status</Status>
817 <Endpoints>
818 <Endpoint>storage-service-blob-endpoint</Endpoint>
819 <Endpoint>storage-service-queue-endpoint</Endpoint>
820 <Endpoint>storage-service-table-endpoint</Endpoint>
821 </Endpoints>
822 <GeoReplicationEnabled>true|false</GeoReplicationEnabled>
823 <GeoPrimaryRegion>primary-region</GeoPrimaryRegion>
824 <StatusOfPrimary>Available|Unavailable</StatusOfPrimary>
825 <LastGeoFailoverTime>DateTime</LastGeoFailoverTime>
826 <GeoSecondaryRegion>secondary-region</GeoSecondaryRegion>
827 <StatusOfSecondary>Available|Unavailable</StatusOfSecondary>
828 <CreationTime>time-of-creation</CreationTime>
829 </StorageServiceProperties>
830 <ExtendedProperties>
831 <ExtendedProperty>
832 <Name>property-name</Name>
833 <Value>property-value</Value>
834 </ExtendedProperty>
835 </ExtendedProperties>
836 </StorageService>
837 </StorageServices>
838 `)
839 rigFixedResponseDispatcher(&x509Response{
840 StatusCode: http.StatusOK,
841 Body: []byte(body),
842 })
843 api := makeAPI(c)
844 recordedRequests := make([]*X509Request, 0)
845 rigRecordingDispatcher(&recordedRequests)
846
847 accounts, err := api.ListStorageAccounts()
848
849 c.Assert(err, IsNil)
850 c.Assert(recordedRequests, HasLen, 1)
851 expectedURL := api.session.composeURL("services/storageservices")
852 c.Assert(recordedRequests[0].URL, Equals, expectedURL)
853 c.Check(accounts, HasLen, 1)
854 c.Check(accounts[0], DeepEquals, StorageService{
855 URL: "storage-service-address",
856 ServiceName: "storage-service-name",
857 Description: "description",
858 AffinityGroup: "affinity-group",
859 Label: "base64-encoded-label",
860 Status: "status",
861 Endpoints: []string{
862 "storage-service-blob-endpoint",
863 "storage-service-queue-endpoint",
864 "storage-service-table-endpoint",
865 },
866 GeoReplicationEnabled: "true|false",
867 GeoPrimaryRegion: "primary-region",
868 StatusOfPrimary: "Available|Unavailable",
869 LastGeoFailoverTime: "DateTime",
870 GeoSecondaryRegion: "secondary-region",
871 StatusOfSecondary: "Available|Unavailable",
872 CreationTime: "time-of-creation",
873 ExtendedProperties: []ExtendedProperty{
874 {"property-name", "property-value"},
875 },
876 })
877}
878
805func (suite *managementBaseAPISuite) TestAddStorageAccount(c *C) {879func (suite *managementBaseAPISuite) TestAddStorageAccount(c *C) {
806 api := makeAPI(c)880 api := makeAPI(c)
807 header := http.Header{}881 header := http.Header{}
@@ -872,6 +946,36 @@
872 c.Check(keys.URL, Equals, url)946 c.Check(keys.URL, Equals, url)
873}947}
874948
949func (suite *managementBaseAPISuite) TestListDisks(c *C) {
950 // This is a minimal response body; Disks XML docs are tested elsewhere.
951 responseBody := `
952 <Disks xmlns="http://schemas.microsoft.com/windowsazure">
953 <Disk>
954 <Name>disk-name-1</Name>
955 </Disk>
956 <Disk>
957 <Name>disk-name-2</Name>
958 </Disk>
959 </Disks>`
960 rigFixedResponseDispatcher(&x509Response{
961 StatusCode: http.StatusOK,
962 Body: []byte(responseBody),
963 })
964 recordedRequests := make([]*X509Request, 0)
965 rigRecordingDispatcher(&recordedRequests)
966
967 disks, err := makeAPI(c).ListDisks()
968
969 c.Assert(err, IsNil)
970 c.Assert(recordedRequests, HasLen, 1)
971 c.Check(recordedRequests[0].Method, Equals, "GET")
972 c.Check(recordedRequests[0].URL, Equals, AZURE_URL+"subscriptionId/services/disks")
973 c.Check(disks, HasLen, 2)
974 c.Check(disks, DeepEquals, []Disk{
975 {Name: "disk-name-1"}, {Name: "disk-name-2"},
976 })
977}
978
875func assertDeleteDiskRequest(c *C, api *ManagementAPI, diskName string, httpRequest *X509Request) {979func assertDeleteDiskRequest(c *C, api *ManagementAPI, diskName string, httpRequest *X509Request) {
876 expectedURL := fmt.Sprintf("%s%s/services/disks/%s", AZURE_URL,980 expectedURL := fmt.Sprintf("%s%s/services/disks/%s", AZURE_URL,
877 api.session.subscriptionId, diskName)981 api.session.subscriptionId, diskName)
878982
=== modified file 'xmlobjects.go'
--- xmlobjects.go 2013-07-25 22:02:41 +0000
+++ xmlobjects.go 2013-07-29 15:50:39 +0000
@@ -251,6 +251,40 @@
251}251}
252252
253//253//
254// Disk, as returned by List Disks
255//
256
257type AttachedTo struct {
258 HostedServiceName string `xml:"HostedServiceName"`
259 DeploymentName string `xml:"DeploymentName"`
260 RoleName string `xml:"RoleName"`
261}
262
263type Disk struct {
264 AffinityGroup string `xml:"AffinityGroup,omitempty"`
265 AttachedTo *AttachedTo `xml:"AttachedTo,omitempty"`
266 OS string `xml:"OS,omitempty"`
267 Location string `xml:"Location,omitempty`
268 LogicalSizeInGB string `xml:"LogicalSizeInGB,omitempty"`
269 MediaLink string `xml:"MediaLink,omitempty"`
270 Name string `xml:"Name"`
271 SourceImageName string `xml:"SourceImageName,omitempty"`
272}
273
274type Disks struct {
275 XMLNS string `xml:"xmlns,attr"`
276 Disks []Disk `xml:"Disk"`
277}
278
279func (disks *Disks) Serialize() (string, error) {
280 return toxml(disks)
281}
282
283func (disks *Disks) Deserialize(data []byte) error {
284 return xml.Unmarshal(data, disks)
285}
286
287//
254// DataVirtualHardDisk288// DataVirtualHardDisk
255//289//
256290
@@ -764,7 +798,8 @@
764 LastGeoFailoverTime string `xml:"StorageServiceProperties>LastGeoFailoverTime"`798 LastGeoFailoverTime string `xml:"StorageServiceProperties>LastGeoFailoverTime"`
765 GeoSecondaryRegion string `xml:"StorageServiceProperties>GeoSecondaryRegion"`799 GeoSecondaryRegion string `xml:"StorageServiceProperties>GeoSecondaryRegion"`
766 StatusOfSecondary string `xml:"StorageServiceProperties>StatusOfSecondary"`800 StatusOfSecondary string `xml:"StorageServiceProperties>StatusOfSecondary"`
767 ExtendedProperties []ExtendedProperty `xml:"StorageServiceProperties>ExtendedProperties>ExtendedProperty"`801 CreationTime string `xml:"StorageServiceProperties>CreationTime"`
802 ExtendedProperties []ExtendedProperty `xml:"ExtendedProperties>ExtendedProperty"`
768803
769 // TODO: Add accessors for non-string data encoded as strings.804 // TODO: Add accessors for non-string data encoded as strings.
770}805}
771806
=== modified file 'xmlobjects_test.go'
--- xmlobjects_test.go 2013-07-25 22:02:41 +0000
+++ xmlobjects_test.go 2013-07-29 15:50:39 +0000
@@ -67,6 +67,94 @@
67 c.Check(strings.TrimSpace(xml), Equals, strings.TrimSpace(expected))67 c.Check(strings.TrimSpace(xml), Equals, strings.TrimSpace(expected))
68}68}
6969
70func (suite *xmlSuite) TestDisksSerialize(c *C) {
71 disks := &Disks{
72 XMLNS: XMLNS,
73 Disks: []Disk{
74 {
75 AffinityGroup: "affinity-group",
76 AttachedTo: &AttachedTo{
77 HostedServiceName: "hosted-service-name",
78 DeploymentName: "deployment-name",
79 RoleName: "role-name",
80 },
81 OS: "os",
82 Location: "location",
83 LogicalSizeInGB: "logical-size-in-gb",
84 MediaLink: "media-link",
85 Name: "name",
86 SourceImageName: "source-image-name",
87 },
88 },
89 }
90 observed, err := disks.Serialize()
91 c.Assert(err, IsNil)
92 expected := strings.TrimSpace(dedent.Dedent(`
93 <Disks xmlns="http://schemas.microsoft.com/windowsazure">
94 <Disk>
95 <AffinityGroup>affinity-group</AffinityGroup>
96 <AttachedTo>
97 <HostedServiceName>hosted-service-name</HostedServiceName>
98 <DeploymentName>deployment-name</DeploymentName>
99 <RoleName>role-name</RoleName>
100 </AttachedTo>
101 <OS>os</OS>
102 <Location>location</Location>
103 <LogicalSizeInGB>logical-size-in-gb</LogicalSizeInGB>
104 <MediaLink>media-link</MediaLink>
105 <Name>name</Name>
106 <SourceImageName>source-image-name</SourceImageName>
107 </Disk>
108 </Disks>`))
109 c.Check(observed, Equals, expected)
110
111}
112
113func (suite *xmlSuite) TestDisksDeserialize(c *C) {
114 // From http://msdn.microsoft.com/en-us/library/windowsazure/jj157176.aspx
115 input := `
116 <Disks xmlns="http://schemas.microsoft.com/windowsazure">
117 <Disk>
118 <AffinityGroup>affinity-group-name</AffinityGroup>
119 <AttachedTo>
120 <HostedServiceName>hostedService-name</HostedServiceName>
121 <DeploymentName>deployment-name</DeploymentName>
122 <RoleName>role-name</RoleName>
123 </AttachedTo>
124 <OS>Linux|Windows</OS>
125 <Location>geo-location-of-the-stored-disk</Location>
126 <LogicalSizeInGB>size-of-the-disk</LogicalSizeInGB>
127 <MediaLink>uri-of-the-containing-blob</MediaLink>
128 <Name>disk-name</Name>
129 <SourceImageName>source-image-name</SourceImageName>
130 </Disk>
131 ...
132 </Disks>`
133 expected := &Disks{
134 XMLNS: XMLNS,
135 Disks: []Disk{
136 {
137 AffinityGroup: "affinity-group-name",
138 AttachedTo: &AttachedTo{
139 HostedServiceName: "hostedService-name",
140 DeploymentName: "deployment-name",
141 RoleName: "role-name",
142 },
143 OS: "Linux|Windows",
144 Location: "geo-location-of-the-stored-disk",
145 LogicalSizeInGB: "size-of-the-disk",
146 MediaLink: "uri-of-the-containing-blob",
147 Name: "disk-name",
148 SourceImageName: "source-image-name",
149 },
150 },
151 }
152 observed := &Disks{}
153 err := observed.Deserialize([]byte(input))
154 c.Assert(err, IsNil)
155 c.Check(observed, DeepEquals, expected)
156}
157
70func (suite *xmlSuite) TestOSVirtualHardDisk(c *C) {158func (suite *xmlSuite) TestOSVirtualHardDisk(c *C) {
71 disk := makeOSVirtualHardDisk()159 disk := makeOSVirtualHardDisk()
72160
@@ -1113,17 +1201,17 @@
1113 <LastGeoFailoverTime>%s</LastGeoFailoverTime>1201 <LastGeoFailoverTime>%s</LastGeoFailoverTime>
1114 <GeoSecondaryRegion>%s</GeoSecondaryRegion>1202 <GeoSecondaryRegion>%s</GeoSecondaryRegion>
1115 <StatusOfSecondary>%s</StatusOfSecondary>1203 <StatusOfSecondary>%s</StatusOfSecondary>
1116 <ExtendedProperties>
1117 <ExtendedProperty>
1118 <Name>%s</Name>
1119 <Value>%s</Value>
1120 </ExtendedProperty>
1121 <ExtendedProperty>
1122 <Name>%s</Name>
1123 <Value>%s</Value>
1124 </ExtendedProperty>
1125 </ExtendedProperties>
1126 </StorageServiceProperties>1204 </StorageServiceProperties>
1205 <ExtendedProperties>
1206 <ExtendedProperty>
1207 <Name>%s</Name>
1208 <Value>%s</Value>
1209 </ExtendedProperty>
1210 <ExtendedProperty>
1211 <Name>%s</Name>
1212 <Value>%s</Value>
1213 </ExtendedProperty>
1214 </ExtendedProperties>
1127 </StorageService>1215 </StorageService>
1128 </StorageServices>`1216 </StorageServices>`
1129 url := MakeRandomString(10)1217 url := MakeRandomString(10)

Subscribers

People subscribed via source and target branches

to all changes: