github.com/gophercloud/gophercloud@v1.11.0/openstack/compute/v2/servers/results.go (about) 1 package servers 2 3 import ( 4 "crypto/rsa" 5 "encoding/base64" 6 "encoding/json" 7 "fmt" 8 "net/url" 9 "path" 10 "time" 11 12 "github.com/gophercloud/gophercloud" 13 "github.com/gophercloud/gophercloud/pagination" 14 ) 15 16 type serverResult struct { 17 gophercloud.Result 18 } 19 20 // Extract interprets any serverResult as a Server, if possible. 21 func (r serverResult) Extract() (*Server, error) { 22 var s Server 23 err := r.ExtractInto(&s) 24 return &s, err 25 } 26 27 func (r serverResult) ExtractInto(v interface{}) error { 28 return r.Result.ExtractIntoStructPtr(v, "server") 29 } 30 31 func ExtractServersInto(r pagination.Page, v interface{}) error { 32 return r.(ServerPage).Result.ExtractIntoSlicePtr(v, "servers") 33 } 34 35 // CreateResult is the response from a Create operation. Call its Extract 36 // method to interpret it as a Server. 37 type CreateResult struct { 38 serverResult 39 } 40 41 // GetResult is the response from a Get operation. Call its Extract 42 // method to interpret it as a Server. 43 type GetResult struct { 44 serverResult 45 } 46 47 // UpdateResult is the response from an Update operation. Call its Extract 48 // method to interpret it as a Server. 49 type UpdateResult struct { 50 serverResult 51 } 52 53 // DeleteResult is the response from a Delete operation. Call its ExtractErr 54 // method to determine if the call succeeded or failed. 55 type DeleteResult struct { 56 gophercloud.ErrResult 57 } 58 59 // RebuildResult is the response from a Rebuild operation. Call its Extract 60 // method to interpret it as a Server. 61 type RebuildResult struct { 62 serverResult 63 } 64 65 // ActionResult represents the result of server action operations, like reboot. 66 // Call its ExtractErr method to determine if the action succeeded or failed. 67 type ActionResult struct { 68 gophercloud.ErrResult 69 } 70 71 // CreateImageResult is the response from a CreateImage operation. Call its 72 // ExtractImageID method to retrieve the ID of the newly created image. 73 type CreateImageResult struct { 74 gophercloud.Result 75 } 76 77 // ShowConsoleOutputResult represents the result of console output from a server 78 type ShowConsoleOutputResult struct { 79 gophercloud.Result 80 } 81 82 // Extract will return the console output from a ShowConsoleOutput request. 83 func (r ShowConsoleOutputResult) Extract() (string, error) { 84 var s struct { 85 Output string `json:"output"` 86 } 87 88 err := r.ExtractInto(&s) 89 return s.Output, err 90 } 91 92 // GetPasswordResult represent the result of a get os-server-password operation. 93 // Call its ExtractPassword method to retrieve the password. 94 type GetPasswordResult struct { 95 gophercloud.Result 96 } 97 98 // ExtractPassword gets the encrypted password. 99 // If privateKey != nil the password is decrypted with the private key. 100 // If privateKey == nil the encrypted password is returned and can be decrypted 101 // with: 102 // 103 // echo '<pwd>' | base64 -D | openssl rsautl -decrypt -inkey <private_key> 104 func (r GetPasswordResult) ExtractPassword(privateKey *rsa.PrivateKey) (string, error) { 105 var s struct { 106 Password string `json:"password"` 107 } 108 err := r.ExtractInto(&s) 109 if err == nil && privateKey != nil && s.Password != "" { 110 return decryptPassword(s.Password, privateKey) 111 } 112 return s.Password, err 113 } 114 115 func decryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (string, error) { 116 b64EncryptedPassword := make([]byte, base64.StdEncoding.DecodedLen(len(encryptedPassword))) 117 118 n, err := base64.StdEncoding.Decode(b64EncryptedPassword, []byte(encryptedPassword)) 119 if err != nil { 120 return "", fmt.Errorf("Failed to base64 decode encrypted password: %s", err) 121 } 122 password, err := rsa.DecryptPKCS1v15(nil, privateKey, b64EncryptedPassword[0:n]) 123 if err != nil { 124 return "", fmt.Errorf("Failed to decrypt password: %s", err) 125 } 126 127 return string(password), nil 128 } 129 130 // ExtractImageID gets the ID of the newly created server image from the header. 131 func (r CreateImageResult) ExtractImageID() (string, error) { 132 if r.Err != nil { 133 return "", r.Err 134 } 135 // Get the image id from the header 136 u, err := url.ParseRequestURI(r.Header.Get("Location")) 137 if err != nil { 138 return "", err 139 } 140 imageID := path.Base(u.Path) 141 if imageID == "." || imageID == "/" { 142 return "", fmt.Errorf("Failed to parse the ID of newly created image: %s", u) 143 } 144 return imageID, nil 145 } 146 147 // Server represents a server/instance in the OpenStack cloud. 148 type Server struct { 149 // ID uniquely identifies this server amongst all other servers, 150 // including those not accessible to the current tenant. 151 ID string `json:"id"` 152 153 // TenantID identifies the tenant owning this server resource. 154 TenantID string `json:"tenant_id"` 155 156 // UserID uniquely identifies the user account owning the tenant. 157 UserID string `json:"user_id"` 158 159 // Name contains the human-readable name for the server. 160 Name string `json:"name"` 161 162 // Updated and Created contain ISO-8601 timestamps of when the state of the 163 // server last changed, and when it was created. 164 Updated time.Time `json:"updated"` 165 Created time.Time `json:"created"` 166 167 // HostID is the host where the server is located in the cloud. 168 HostID string `json:"hostid"` 169 170 // Status contains the current operational status of the server, 171 // such as IN_PROGRESS or ACTIVE. 172 Status string `json:"status"` 173 174 // Progress ranges from 0..100. 175 // A request made against the server completes only once Progress reaches 100. 176 Progress int `json:"progress"` 177 178 // AccessIPv4 and AccessIPv6 contain the IP addresses of the server, 179 // suitable for remote access for administration. 180 AccessIPv4 string `json:"accessIPv4"` 181 AccessIPv6 string `json:"accessIPv6"` 182 183 // Image refers to a JSON object, which itself indicates the OS image used to 184 // deploy the server. 185 Image map[string]interface{} `json:"-"` 186 187 // Flavor refers to a JSON object, which itself indicates the hardware 188 // configuration of the deployed server. 189 Flavor map[string]interface{} `json:"flavor"` 190 191 // Addresses includes a list of all IP addresses assigned to the server, 192 // keyed by pool. 193 Addresses map[string]interface{} `json:"addresses"` 194 195 // Metadata includes a list of all user-specified key-value pairs attached 196 // to the server. 197 Metadata map[string]string `json:"metadata"` 198 199 // Links includes HTTP references to the itself, useful for passing along to 200 // other APIs that might want a server reference. 201 Links []interface{} `json:"links"` 202 203 // KeyName indicates which public key was injected into the server on launch. 204 KeyName string `json:"key_name"` 205 206 // AdminPass will generally be empty (""). However, it will contain the 207 // administrative password chosen when provisioning a new server without a 208 // set AdminPass setting in the first place. 209 // Note that this is the ONLY time this field will be valid. 210 AdminPass string `json:"adminPass"` 211 212 // SecurityGroups includes the security groups that this instance has applied 213 // to it. 214 SecurityGroups []map[string]interface{} `json:"security_groups"` 215 216 // AttachedVolumes includes the volume attachments of this instance 217 AttachedVolumes []AttachedVolume `json:"os-extended-volumes:volumes_attached"` 218 219 // Fault contains failure information about a server. 220 Fault Fault `json:"fault"` 221 222 // Tags is a slice/list of string tags in a server. 223 // The requires microversion 2.26 or later. 224 Tags *[]string `json:"tags"` 225 226 // ServerGroups is a slice of strings containing the UUIDs of the 227 // server groups to which the server belongs. Currently this can 228 // contain at most one entry. 229 // New in microversion 2.71 230 ServerGroups *[]string `json:"server_groups"` 231 } 232 233 type AttachedVolume struct { 234 ID string `json:"id"` 235 } 236 237 type Fault struct { 238 Code int `json:"code"` 239 Created time.Time `json:"created"` 240 Details string `json:"details"` 241 Message string `json:"message"` 242 } 243 244 func (r *Server) UnmarshalJSON(b []byte) error { 245 type tmp Server 246 var s struct { 247 tmp 248 Image interface{} `json:"image"` 249 } 250 err := json.Unmarshal(b, &s) 251 if err != nil { 252 return err 253 } 254 255 *r = Server(s.tmp) 256 257 switch t := s.Image.(type) { 258 case map[string]interface{}: 259 r.Image = t 260 case string: 261 switch t { 262 case "": 263 r.Image = nil 264 } 265 } 266 267 return err 268 } 269 270 // ServerPage abstracts the raw results of making a List() request against 271 // the API. As OpenStack extensions may freely alter the response bodies of 272 // structures returned to the client, you may only safely access the data 273 // provided through the ExtractServers call. 274 type ServerPage struct { 275 pagination.LinkedPageBase 276 } 277 278 // IsEmpty returns true if a page contains no Server results. 279 func (r ServerPage) IsEmpty() (bool, error) { 280 if r.StatusCode == 204 { 281 return true, nil 282 } 283 284 s, err := ExtractServers(r) 285 return len(s) == 0, err 286 } 287 288 // NextPageURL uses the response's embedded link reference to navigate to the 289 // next page of results. 290 func (r ServerPage) NextPageURL() (string, error) { 291 var s struct { 292 Links []gophercloud.Link `json:"servers_links"` 293 } 294 err := r.ExtractInto(&s) 295 if err != nil { 296 return "", err 297 } 298 return gophercloud.ExtractNextURL(s.Links) 299 } 300 301 // ExtractServers interprets the results of a single page from a List() call, 302 // producing a slice of Server entities. 303 func ExtractServers(r pagination.Page) ([]Server, error) { 304 var s []Server 305 err := ExtractServersInto(r, &s) 306 return s, err 307 } 308 309 // MetadataResult contains the result of a call for (potentially) multiple 310 // key-value pairs. Call its Extract method to interpret it as a 311 // map[string]interface. 312 type MetadataResult struct { 313 gophercloud.Result 314 } 315 316 // GetMetadataResult contains the result of a Get operation. Call its Extract 317 // method to interpret it as a map[string]interface. 318 type GetMetadataResult struct { 319 MetadataResult 320 } 321 322 // ResetMetadataResult contains the result of a Reset operation. Call its 323 // Extract method to interpret it as a map[string]interface. 324 type ResetMetadataResult struct { 325 MetadataResult 326 } 327 328 // UpdateMetadataResult contains the result of an Update operation. Call its 329 // Extract method to interpret it as a map[string]interface. 330 type UpdateMetadataResult struct { 331 MetadataResult 332 } 333 334 // MetadatumResult contains the result of a call for individual a single 335 // key-value pair. 336 type MetadatumResult struct { 337 gophercloud.Result 338 } 339 340 // GetMetadatumResult contains the result of a Get operation. Call its Extract 341 // method to interpret it as a map[string]interface. 342 type GetMetadatumResult struct { 343 MetadatumResult 344 } 345 346 // CreateMetadatumResult contains the result of a Create operation. Call its 347 // Extract method to interpret it as a map[string]interface. 348 type CreateMetadatumResult struct { 349 MetadatumResult 350 } 351 352 // DeleteMetadatumResult contains the result of a Delete operation. Call its 353 // ExtractErr method to determine if the call succeeded or failed. 354 type DeleteMetadatumResult struct { 355 gophercloud.ErrResult 356 } 357 358 // Extract interprets any MetadataResult as a Metadata, if possible. 359 func (r MetadataResult) Extract() (map[string]string, error) { 360 var s struct { 361 Metadata map[string]string `json:"metadata"` 362 } 363 err := r.ExtractInto(&s) 364 return s.Metadata, err 365 } 366 367 // Extract interprets any MetadatumResult as a Metadatum, if possible. 368 func (r MetadatumResult) Extract() (map[string]string, error) { 369 var s struct { 370 Metadatum map[string]string `json:"meta"` 371 } 372 err := r.ExtractInto(&s) 373 return s.Metadatum, err 374 } 375 376 // Address represents an IP address. 377 type Address struct { 378 Version int `json:"version"` 379 Address string `json:"addr"` 380 } 381 382 // AddressPage abstracts the raw results of making a ListAddresses() request 383 // against the API. As OpenStack extensions may freely alter the response bodies 384 // of structures returned to the client, you may only safely access the data 385 // provided through the ExtractAddresses call. 386 type AddressPage struct { 387 pagination.SinglePageBase 388 } 389 390 // IsEmpty returns true if an AddressPage contains no networks. 391 func (r AddressPage) IsEmpty() (bool, error) { 392 if r.StatusCode == 204 { 393 return true, nil 394 } 395 396 addresses, err := ExtractAddresses(r) 397 return len(addresses) == 0, err 398 } 399 400 // ExtractAddresses interprets the results of a single page from a 401 // ListAddresses() call, producing a map of addresses. 402 func ExtractAddresses(r pagination.Page) (map[string][]Address, error) { 403 var s struct { 404 Addresses map[string][]Address `json:"addresses"` 405 } 406 err := (r.(AddressPage)).ExtractInto(&s) 407 return s.Addresses, err 408 } 409 410 // NetworkAddressPage abstracts the raw results of making a 411 // ListAddressesByNetwork() request against the API. 412 // As OpenStack extensions may freely alter the response bodies of structures 413 // returned to the client, you may only safely access the data provided through 414 // the ExtractAddresses call. 415 type NetworkAddressPage struct { 416 pagination.SinglePageBase 417 } 418 419 // IsEmpty returns true if a NetworkAddressPage contains no addresses. 420 func (r NetworkAddressPage) IsEmpty() (bool, error) { 421 if r.StatusCode == 204 { 422 return true, nil 423 } 424 425 addresses, err := ExtractNetworkAddresses(r) 426 return len(addresses) == 0, err 427 } 428 429 // ExtractNetworkAddresses interprets the results of a single page from a 430 // ListAddressesByNetwork() call, producing a slice of addresses. 431 func ExtractNetworkAddresses(r pagination.Page) ([]Address, error) { 432 var s map[string][]Address 433 err := (r.(NetworkAddressPage)).ExtractInto(&s) 434 if err != nil { 435 return nil, err 436 } 437 438 var key string 439 for k := range s { 440 key = k 441 } 442 443 return s[key], err 444 }