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