1package servers 2 3import ( 4 "reflect" 5 6 "github.com/mitchellh/mapstructure" 7 "github.com/rackspace/gophercloud" 8 "github.com/rackspace/gophercloud/pagination" 9) 10 11type serverResult struct { 12 gophercloud.Result 13} 14 15// Extract interprets any serverResult as a Server, if possible. 16func (r serverResult) Extract() (*Server, error) { 17 if r.Err != nil { 18 return nil, r.Err 19 } 20 21 var response struct { 22 Server Server `mapstructure:"server"` 23 } 24 25 config := &mapstructure.DecoderConfig{ 26 DecodeHook: toMapFromString, 27 Result: &response, 28 } 29 decoder, err := mapstructure.NewDecoder(config) 30 if err != nil { 31 return nil, err 32 } 33 34 err = decoder.Decode(r.Body) 35 if err != nil { 36 return nil, err 37 } 38 39 return &response.Server, nil 40} 41 42// CreateResult temporarily contains the response from a Create call. 43type CreateResult struct { 44 serverResult 45} 46 47// GetResult temporarily contains the response from a Get call. 48type GetResult struct { 49 serverResult 50} 51 52// UpdateResult temporarily contains the response from an Update call. 53type UpdateResult struct { 54 serverResult 55} 56 57// DeleteResult temporarily contains the response from a Delete call. 58type DeleteResult struct { 59 gophercloud.ErrResult 60} 61 62// RebuildResult temporarily contains the response from a Rebuild call. 63type RebuildResult struct { 64 serverResult 65} 66 67// ActionResult represents the result of server action operations, like reboot 68type ActionResult struct { 69 gophercloud.ErrResult 70} 71 72// RescueResult represents the result of a server rescue operation 73type RescueResult struct { 74 ActionResult 75} 76 77// Extract interprets any RescueResult as an AdminPass, if possible. 78func (r RescueResult) Extract() (string, error) { 79 if r.Err != nil { 80 return "", r.Err 81 } 82 83 var response struct { 84 AdminPass string `mapstructure:"adminPass"` 85 } 86 87 err := mapstructure.Decode(r.Body, &response) 88 return response.AdminPass, err 89} 90 91// Server exposes only the standard OpenStack fields corresponding to a given server on the user's account. 92type Server struct { 93 // ID uniquely identifies this server amongst all other servers, including those not accessible to the current tenant. 94 ID string 95 96 // TenantID identifies the tenant owning this server resource. 97 TenantID string `mapstructure:"tenant_id"` 98 99 // UserID uniquely identifies the user account owning the tenant. 100 UserID string `mapstructure:"user_id"` 101 102 // Name contains the human-readable name for the server. 103 Name string 104 105 // Updated and Created contain ISO-8601 timestamps of when the state of the server last changed, and when it was created. 106 Updated string 107 Created string 108 109 HostID string 110 111 // Status contains the current operational status of the server, such as IN_PROGRESS or ACTIVE. 112 Status string 113 114 // Progress ranges from 0..100. 115 // A request made against the server completes only once Progress reaches 100. 116 Progress int 117 118 // AccessIPv4 and AccessIPv6 contain the IP addresses of the server, suitable for remote access for administration. 119 AccessIPv4, AccessIPv6 string 120 121 // Image refers to a JSON object, which itself indicates the OS image used to deploy the server. 122 Image map[string]interface{} 123 124 // Flavor refers to a JSON object, which itself indicates the hardware configuration of the deployed server. 125 Flavor map[string]interface{} 126 127 // Addresses includes a list of all IP addresses assigned to the server, keyed by pool. 128 Addresses map[string]interface{} 129 130 // Metadata includes a list of all user-specified key-value pairs attached to the server. 131 Metadata map[string]interface{} 132 133 // Links includes HTTP references to the itself, useful for passing along to other APIs that might want a server reference. 134 Links []interface{} 135 136 // KeyName indicates which public key was injected into the server on launch. 137 KeyName string `json:"key_name" mapstructure:"key_name"` 138 139 // AdminPass will generally be empty (""). However, it will contain the administrative password chosen when provisioning a new server without a set AdminPass setting in the first place. 140 // Note that this is the ONLY time this field will be valid. 141 AdminPass string `json:"adminPass" mapstructure:"adminPass"` 142 143 // SecurityGroups includes the security groups that this instance has applied to it 144 SecurityGroups []map[string]interface{} `json:"security_groups" mapstructure:"security_groups"` 145} 146 147// ServerPage abstracts the raw results of making a List() request against the API. 148// As OpenStack extensions may freely alter the response bodies of structures returned to the client, you may only safely access the 149// data provided through the ExtractServers call. 150type ServerPage struct { 151 pagination.LinkedPageBase 152} 153 154// IsEmpty returns true if a page contains no Server results. 155func (page ServerPage) IsEmpty() (bool, error) { 156 servers, err := ExtractServers(page) 157 if err != nil { 158 return true, err 159 } 160 return len(servers) == 0, nil 161} 162 163// NextPageURL uses the response's embedded link reference to navigate to the next page of results. 164func (page ServerPage) NextPageURL() (string, error) { 165 type resp struct { 166 Links []gophercloud.Link `mapstructure:"servers_links"` 167 } 168 169 var r resp 170 err := mapstructure.Decode(page.Body, &r) 171 if err != nil { 172 return "", err 173 } 174 175 return gophercloud.ExtractNextURL(r.Links) 176} 177 178// ExtractServers interprets the results of a single page from a List() call, producing a slice of Server entities. 179func ExtractServers(page pagination.Page) ([]Server, error) { 180 casted := page.(ServerPage).Body 181 182 var response struct { 183 Servers []Server `mapstructure:"servers"` 184 } 185 186 config := &mapstructure.DecoderConfig{ 187 DecodeHook: toMapFromString, 188 Result: &response, 189 } 190 decoder, err := mapstructure.NewDecoder(config) 191 if err != nil { 192 return nil, err 193 } 194 195 err = decoder.Decode(casted) 196 197 return response.Servers, err 198} 199 200// MetadataResult contains the result of a call for (potentially) multiple key-value pairs. 201type MetadataResult struct { 202 gophercloud.Result 203} 204 205// GetMetadataResult temporarily contains the response from a metadata Get call. 206type GetMetadataResult struct { 207 MetadataResult 208} 209 210// ResetMetadataResult temporarily contains the response from a metadata Reset call. 211type ResetMetadataResult struct { 212 MetadataResult 213} 214 215// UpdateMetadataResult temporarily contains the response from a metadata Update call. 216type UpdateMetadataResult struct { 217 MetadataResult 218} 219 220// MetadatumResult contains the result of a call for individual a single key-value pair. 221type MetadatumResult struct { 222 gophercloud.Result 223} 224 225// GetMetadatumResult temporarily contains the response from a metadatum Get call. 226type GetMetadatumResult struct { 227 MetadatumResult 228} 229 230// CreateMetadatumResult temporarily contains the response from a metadatum Create call. 231type CreateMetadatumResult struct { 232 MetadatumResult 233} 234 235// DeleteMetadatumResult temporarily contains the response from a metadatum Delete call. 236type DeleteMetadatumResult struct { 237 gophercloud.ErrResult 238} 239 240// Extract interprets any MetadataResult as a Metadata, if possible. 241func (r MetadataResult) Extract() (map[string]string, error) { 242 if r.Err != nil { 243 return nil, r.Err 244 } 245 246 var response struct { 247 Metadata map[string]string `mapstructure:"metadata"` 248 } 249 250 err := mapstructure.Decode(r.Body, &response) 251 return response.Metadata, err 252} 253 254// Extract interprets any MetadatumResult as a Metadatum, if possible. 255func (r MetadatumResult) Extract() (map[string]string, error) { 256 if r.Err != nil { 257 return nil, r.Err 258 } 259 260 var response struct { 261 Metadatum map[string]string `mapstructure:"meta"` 262 } 263 264 err := mapstructure.Decode(r.Body, &response) 265 return response.Metadatum, err 266} 267 268func toMapFromString(from reflect.Kind, to reflect.Kind, data interface{}) (interface{}, error) { 269 if (from == reflect.String) && (to == reflect.Map) { 270 return map[string]interface{}{}, nil 271 } 272 return data, nil 273} 274 275// Address represents an IP address. 276type Address struct { 277 Version int `mapstructure:"version"` 278 Address string `mapstructure:"addr"` 279} 280 281// AddressPage abstracts the raw results of making a ListAddresses() request against the API. 282// As OpenStack extensions may freely alter the response bodies of structures returned 283// to the client, you may only safely access the data provided through the ExtractAddresses call. 284type AddressPage struct { 285 pagination.SinglePageBase 286} 287 288// IsEmpty returns true if an AddressPage contains no networks. 289func (r AddressPage) IsEmpty() (bool, error) { 290 addresses, err := ExtractAddresses(r) 291 if err != nil { 292 return true, err 293 } 294 return len(addresses) == 0, nil 295} 296 297// ExtractAddresses interprets the results of a single page from a ListAddresses() call, 298// producing a map of addresses. 299func ExtractAddresses(page pagination.Page) (map[string][]Address, error) { 300 casted := page.(AddressPage).Body 301 302 var response struct { 303 Addresses map[string][]Address `mapstructure:"addresses"` 304 } 305 306 err := mapstructure.Decode(casted, &response) 307 if err != nil { 308 return nil, err 309 } 310 311 return response.Addresses, err 312} 313 314// NetworkAddressPage abstracts the raw results of making a ListAddressesByNetwork() request against the API. 315// As OpenStack extensions may freely alter the response bodies of structures returned 316// to the client, you may only safely access the data provided through the ExtractAddresses call. 317type NetworkAddressPage struct { 318 pagination.SinglePageBase 319} 320 321// IsEmpty returns true if a NetworkAddressPage contains no addresses. 322func (r NetworkAddressPage) IsEmpty() (bool, error) { 323 addresses, err := ExtractNetworkAddresses(r) 324 if err != nil { 325 return true, err 326 } 327 return len(addresses) == 0, nil 328} 329 330// ExtractNetworkAddresses interprets the results of a single page from a ListAddressesByNetwork() call, 331// producing a slice of addresses. 332func ExtractNetworkAddresses(page pagination.Page) ([]Address, error) { 333 casted := page.(NetworkAddressPage).Body 334 335 var response map[string][]Address 336 err := mapstructure.Decode(casted, &response) 337 if err != nil { 338 return nil, err 339 } 340 341 var key string 342 for k := range response { 343 key = k 344 } 345 346 return response[key], err 347} 348