github.com/scaleway/scaleway-cli@v1.11.1/pkg/api/api.go (about) 1 // Copyright (C) 2015 Scaleway. All rights reserved. 2 // Use of this source code is governed by a MIT-style 3 // license that can be found in the LICENSE.md file. 4 5 // Interact with Scaleway API 6 7 // Package api contains client and functions to interact with Scaleway API 8 package api 9 10 import ( 11 "bytes" 12 "crypto/tls" 13 "encoding/json" 14 "errors" 15 "fmt" 16 "io" 17 "io/ioutil" 18 "net" 19 "net/http" 20 "net/http/httputil" 21 "net/url" 22 "os" 23 "sort" 24 "strconv" 25 "strings" 26 "text/tabwriter" 27 "text/template" 28 "time" 29 30 "golang.org/x/sync/errgroup" 31 ) 32 33 // Default values 34 var ( 35 AccountAPI = "https://account.scaleway.com/" 36 MetadataAPI = "http://169.254.42.42/" 37 MarketplaceAPI = "https://api-marketplace.scaleway.com" 38 ComputeAPIPar1 = "https://cp-par1.scaleway.com/" 39 ComputeAPIAms1 = "https://cp-ams1.scaleway.com" 40 41 URLPublicDNS = ".pub.cloud.scaleway.com" 42 URLPrivateDNS = ".priv.cloud.scaleway.com" 43 ) 44 45 func init() { 46 if url := os.Getenv("SCW_ACCOUNT_API"); url != "" { 47 AccountAPI = url 48 } 49 if url := os.Getenv("SCW_METADATA_API"); url != "" { 50 MetadataAPI = url 51 } 52 if url := os.Getenv("SCW_MARKETPLACE_API"); url != "" { 53 MarketplaceAPI = url 54 } 55 } 56 57 const ( 58 perPage = 50 59 ) 60 61 // ScalewayAPI is the interface used to communicate with the Scaleway API 62 type ScalewayAPI struct { 63 // Organization is the identifier of the Scaleway organization 64 Organization string 65 66 // Token is the authentication token for the Scaleway organization 67 Token string 68 69 // Password is the authentication password 70 password string 71 72 userAgent string 73 74 // Cache is used to quickly resolve identifiers from names 75 Cache *ScalewayCache 76 77 client *http.Client 78 verbose bool 79 computeAPI string 80 81 Region string 82 // 83 Logger 84 } 85 86 // ScalewayAPIError represents a Scaleway API Error 87 type ScalewayAPIError struct { 88 // Message is a human-friendly error message 89 APIMessage string `json:"message,omitempty"` 90 91 // Type is a string code that defines the kind of error 92 Type string `json:"type,omitempty"` 93 94 // Fields contains detail about validation error 95 Fields map[string][]string `json:"fields,omitempty"` 96 97 // StatusCode is the HTTP status code received 98 StatusCode int `json:"-"` 99 100 // Message 101 Message string `json:"-"` 102 } 103 104 // Error returns a string representing the error 105 func (e ScalewayAPIError) Error() string { 106 var b bytes.Buffer 107 108 fmt.Fprintf(&b, "StatusCode: %v, ", e.StatusCode) 109 fmt.Fprintf(&b, "Type: %v, ", e.Type) 110 fmt.Fprintf(&b, "APIMessage: \x1b[31m%v\x1b[0m", e.APIMessage) 111 if len(e.Fields) > 0 { 112 fmt.Fprintf(&b, ", Details: %v", e.Fields) 113 } 114 return b.String() 115 } 116 117 // HideAPICredentials removes API credentials from a string 118 func (s *ScalewayAPI) HideAPICredentials(input string) string { 119 output := input 120 if s.Token != "" { 121 output = strings.Replace(output, s.Token, "00000000-0000-4000-8000-000000000000", -1) 122 } 123 if s.Organization != "" { 124 output = strings.Replace(output, s.Organization, "00000000-0000-5000-9000-000000000000", -1) 125 } 126 if s.password != "" { 127 output = strings.Replace(output, s.password, "XX-XX-XX-XX", -1) 128 } 129 return output 130 } 131 132 // ScalewayIPAddress represents a Scaleway IP address 133 type ScalewayIPAddress struct { 134 // Identifier is a unique identifier for the IP address 135 Identifier string `json:"id,omitempty"` 136 137 // IP is an IPv4 address 138 IP string `json:"address,omitempty"` 139 140 // Dynamic is a flag that defines an IP that change on each reboot 141 Dynamic *bool `json:"dynamic,omitempty"` 142 } 143 144 // ScalewayVolume represents a Scaleway Volume 145 type ScalewayVolume struct { 146 // Identifier is a unique identifier for the volume 147 Identifier string `json:"id,omitempty"` 148 149 // Size is the allocated size of the volume 150 Size uint64 `json:"size,omitempty"` 151 152 // CreationDate is the creation date of the volume 153 CreationDate string `json:"creation_date,omitempty"` 154 155 // ModificationDate is the date of the last modification of the volume 156 ModificationDate string `json:"modification_date,omitempty"` 157 158 // Organization is the organization owning the volume 159 Organization string `json:"organization,omitempty"` 160 161 // Name is the name of the volume 162 Name string `json:"name,omitempty"` 163 164 // Server is the server using this image 165 Server *struct { 166 Identifier string `json:"id,omitempty"` 167 Name string `json:"name,omitempty"` 168 } `json:"server,omitempty"` 169 170 // VolumeType is a Scaleway identifier for the kind of volume (default: l_ssd) 171 VolumeType string `json:"volume_type,omitempty"` 172 173 // ExportURI represents the url used by initrd/scripts to attach the volume 174 ExportURI string `json:"export_uri,omitempty"` 175 } 176 177 // ScalewayOneVolume represents the response of a GET /volumes/UUID API call 178 type ScalewayOneVolume struct { 179 Volume ScalewayVolume `json:"volume,omitempty"` 180 } 181 182 // ScalewayVolumes represents a group of Scaleway volumes 183 type ScalewayVolumes struct { 184 // Volumes holds scaleway volumes of the response 185 Volumes []ScalewayVolume `json:"volumes,omitempty"` 186 } 187 188 // ScalewayVolumeDefinition represents a Scaleway volume definition 189 type ScalewayVolumeDefinition struct { 190 // Name is the user-defined name of the volume 191 Name string `json:"name"` 192 193 // Image is the image used by the volume 194 Size uint64 `json:"size"` 195 196 // Bootscript is the bootscript used by the volume 197 Type string `json:"volume_type"` 198 199 // Organization is the owner of the volume 200 Organization string `json:"organization"` 201 } 202 203 // ScalewayVolumePutDefinition represents a Scaleway volume with nullable fields (for PUT) 204 type ScalewayVolumePutDefinition struct { 205 Identifier *string `json:"id,omitempty"` 206 Size *uint64 `json:"size,omitempty"` 207 CreationDate *string `json:"creation_date,omitempty"` 208 ModificationDate *string `json:"modification_date,omitempty"` 209 Organization *string `json:"organization,omitempty"` 210 Name *string `json:"name,omitempty"` 211 Server struct { 212 Identifier *string `json:"id,omitempty"` 213 Name *string `json:"name,omitempty"` 214 } `json:"server,omitempty"` 215 VolumeType *string `json:"volume_type,omitempty"` 216 ExportURI *string `json:"export_uri,omitempty"` 217 } 218 219 // ScalewayImage represents a Scaleway Image 220 type ScalewayImage struct { 221 // Identifier is a unique identifier for the image 222 Identifier string `json:"id,omitempty"` 223 224 // Name is a user-defined name for the image 225 Name string `json:"name,omitempty"` 226 227 // CreationDate is the creation date of the image 228 CreationDate string `json:"creation_date,omitempty"` 229 230 // ModificationDate is the date of the last modification of the image 231 ModificationDate string `json:"modification_date,omitempty"` 232 233 // RootVolume is the root volume bound to the image 234 RootVolume ScalewayVolume `json:"root_volume,omitempty"` 235 236 // Public is true for public images and false for user images 237 Public bool `json:"public,omitempty"` 238 239 // Bootscript is the bootscript bound to the image 240 DefaultBootscript *ScalewayBootscript `json:"default_bootscript,omitempty"` 241 242 // Organization is the owner of the image 243 Organization string `json:"organization,omitempty"` 244 245 // Arch is the architecture target of the image 246 Arch string `json:"arch,omitempty"` 247 248 // FIXME: extra_volumes 249 } 250 251 // ScalewayImageIdentifier represents a Scaleway Image Identifier 252 type ScalewayImageIdentifier struct { 253 Identifier string 254 Arch string 255 Region string 256 Owner string 257 } 258 259 // ScalewayOneImage represents the response of a GET /images/UUID API call 260 type ScalewayOneImage struct { 261 Image ScalewayImage `json:"image,omitempty"` 262 } 263 264 // ScalewayImages represents a group of Scaleway images 265 type ScalewayImages struct { 266 // Images holds scaleway images of the response 267 Images []ScalewayImage `json:"images,omitempty"` 268 } 269 270 // ScalewaySnapshot represents a Scaleway Snapshot 271 type ScalewaySnapshot struct { 272 // Identifier is a unique identifier for the snapshot 273 Identifier string `json:"id,omitempty"` 274 275 // Name is a user-defined name for the snapshot 276 Name string `json:"name,omitempty"` 277 278 // CreationDate is the creation date of the snapshot 279 CreationDate string `json:"creation_date,omitempty"` 280 281 // ModificationDate is the date of the last modification of the snapshot 282 ModificationDate string `json:"modification_date,omitempty"` 283 284 // Size is the allocated size of the volume 285 Size uint64 `json:"size,omitempty"` 286 287 // Organization is the owner of the snapshot 288 Organization string `json:"organization"` 289 290 // State is the current state of the snapshot 291 State string `json:"state"` 292 293 // VolumeType is the kind of volume behind the snapshot 294 VolumeType string `json:"volume_type"` 295 296 // BaseVolume is the volume from which the snapshot inherits 297 BaseVolume ScalewayVolume `json:"base_volume,omitempty"` 298 } 299 300 // ScalewayOneSnapshot represents the response of a GET /snapshots/UUID API call 301 type ScalewayOneSnapshot struct { 302 Snapshot ScalewaySnapshot `json:"snapshot,omitempty"` 303 } 304 305 // ScalewaySnapshots represents a group of Scaleway snapshots 306 type ScalewaySnapshots struct { 307 // Snapshots holds scaleway snapshots of the response 308 Snapshots []ScalewaySnapshot `json:"snapshots,omitempty"` 309 } 310 311 // ScalewayBootscript represents a Scaleway Bootscript 312 type ScalewayBootscript struct { 313 Bootcmdargs string `json:"bootcmdargs,omitempty"` 314 Dtb string `json:"dtb,omitempty"` 315 Initrd string `json:"initrd,omitempty"` 316 Kernel string `json:"kernel,omitempty"` 317 318 // Arch is the architecture target of the bootscript 319 Arch string `json:"architecture,omitempty"` 320 321 // Identifier is a unique identifier for the bootscript 322 Identifier string `json:"id,omitempty"` 323 324 // Organization is the owner of the bootscript 325 Organization string `json:"organization,omitempty"` 326 327 // Name is a user-defined name for the bootscript 328 Title string `json:"title,omitempty"` 329 330 // Public is true for public bootscripts and false for user bootscripts 331 Public bool `json:"public,omitempty"` 332 333 Default bool `json:"default,omitempty"` 334 } 335 336 // ScalewayOneBootscript represents the response of a GET /bootscripts/UUID API call 337 type ScalewayOneBootscript struct { 338 Bootscript ScalewayBootscript `json:"bootscript,omitempty"` 339 } 340 341 // ScalewayBootscripts represents a group of Scaleway bootscripts 342 type ScalewayBootscripts struct { 343 // Bootscripts holds Scaleway bootscripts of the response 344 Bootscripts []ScalewayBootscript `json:"bootscripts,omitempty"` 345 } 346 347 // ScalewayTask represents a Scaleway Task 348 type ScalewayTask struct { 349 // Identifier is a unique identifier for the task 350 Identifier string `json:"id,omitempty"` 351 352 // StartDate is the start date of the task 353 StartDate string `json:"started_at,omitempty"` 354 355 // TerminationDate is the termination date of the task 356 TerminationDate string `json:"terminated_at,omitempty"` 357 358 HrefFrom string `json:"href_from,omitempty"` 359 360 Description string `json:"description,omitempty"` 361 362 Status string `json:"status,omitempty"` 363 364 Progress int `json:"progress,omitempty"` 365 } 366 367 // ScalewayOneTask represents the response of a GET /tasks/UUID API call 368 type ScalewayOneTask struct { 369 Task ScalewayTask `json:"task,omitempty"` 370 } 371 372 // ScalewayTasks represents a group of Scaleway tasks 373 type ScalewayTasks struct { 374 // Tasks holds scaleway tasks of the response 375 Tasks []ScalewayTask `json:"tasks,omitempty"` 376 } 377 378 // ScalewaySecurityGroupRule definition 379 type ScalewaySecurityGroupRule struct { 380 Direction string `json:"direction"` 381 Protocol string `json:"protocol"` 382 IPRange string `json:"ip_range"` 383 DestPortFrom int `json:"dest_port_from,omitempty"` 384 Action string `json:"action"` 385 Position int `json:"position"` 386 DestPortTo string `json:"dest_port_to"` 387 Editable bool `json:"editable"` 388 ID string `json:"id"` 389 } 390 391 // ScalewayGetSecurityGroupRules represents the response of a GET /security_group/{groupID}/rules 392 type ScalewayGetSecurityGroupRules struct { 393 Rules []ScalewaySecurityGroupRule `json:"rules"` 394 } 395 396 // ScalewayGetSecurityGroupRule represents the response of a GET /security_group/{groupID}/rules/{ruleID} 397 type ScalewayGetSecurityGroupRule struct { 398 Rules ScalewaySecurityGroupRule `json:"rule"` 399 } 400 401 // ScalewayNewSecurityGroupRule definition POST/PUT request /security_group/{groupID} 402 type ScalewayNewSecurityGroupRule struct { 403 Action string `json:"action"` 404 Direction string `json:"direction"` 405 IPRange string `json:"ip_range"` 406 Protocol string `json:"protocol"` 407 DestPortFrom int `json:"dest_port_from,omitempty"` 408 } 409 410 // ScalewaySecurityGroups definition 411 type ScalewaySecurityGroups struct { 412 Description string `json:"description"` 413 ID string `json:"id"` 414 Organization string `json:"organization"` 415 Name string `json:"name"` 416 Servers []ScalewaySecurityGroup `json:"servers"` 417 EnableDefaultSecurity bool `json:"enable_default_security"` 418 OrganizationDefault bool `json:"organization_default"` 419 } 420 421 // ScalewayGetSecurityGroups represents the response of a GET /security_groups/ 422 type ScalewayGetSecurityGroups struct { 423 SecurityGroups []ScalewaySecurityGroups `json:"security_groups"` 424 } 425 426 // ScalewayGetSecurityGroup represents the response of a GET /security_groups/{groupID} 427 type ScalewayGetSecurityGroup struct { 428 SecurityGroups ScalewaySecurityGroups `json:"security_group"` 429 } 430 431 // ScalewayIPDefinition represents the IP's fields 432 type ScalewayIPDefinition struct { 433 Organization string `json:"organization"` 434 Reverse *string `json:"reverse"` 435 ID string `json:"id"` 436 Server *struct { 437 Identifier string `json:"id,omitempty"` 438 Name string `json:"name,omitempty"` 439 } `json:"server"` 440 Address string `json:"address"` 441 } 442 443 // ScalewayGetIPS represents the response of a GET /ips/ 444 type ScalewayGetIPS struct { 445 IPS []ScalewayIPDefinition `json:"ips"` 446 } 447 448 // ScalewayGetIP represents the response of a GET /ips/{id_ip} 449 type ScalewayGetIP struct { 450 IP ScalewayIPDefinition `json:"ip"` 451 } 452 453 // ScalewaySecurityGroup represents a Scaleway security group 454 type ScalewaySecurityGroup struct { 455 // Identifier is a unique identifier for the security group 456 Identifier string `json:"id,omitempty"` 457 458 // Name is the user-defined name of the security group 459 Name string `json:"name,omitempty"` 460 } 461 462 // ScalewayNewSecurityGroup definition POST request /security_groups 463 type ScalewayNewSecurityGroup struct { 464 Organization string `json:"organization"` 465 Name string `json:"name"` 466 Description string `json:"description"` 467 } 468 469 // ScalewayUpdateSecurityGroup definition PUT request /security_groups 470 type ScalewayUpdateSecurityGroup struct { 471 Organization string `json:"organization"` 472 Name string `json:"name"` 473 Description string `json:"description"` 474 OrganizationDefault bool `json:"organization_default"` 475 } 476 477 // ScalewayServer represents a Scaleway server 478 type ScalewayServer struct { 479 // Arch is the architecture target of the server 480 Arch string `json:"arch,omitempty"` 481 482 // Identifier is a unique identifier for the server 483 Identifier string `json:"id,omitempty"` 484 485 // Name is the user-defined name of the server 486 Name string `json:"name,omitempty"` 487 488 // CreationDate is the creation date of the server 489 CreationDate string `json:"creation_date,omitempty"` 490 491 // ModificationDate is the date of the last modification of the server 492 ModificationDate string `json:"modification_date,omitempty"` 493 494 // Image is the image used by the server 495 Image ScalewayImage `json:"image,omitempty"` 496 497 // DynamicIPRequired is a flag that defines a server with a dynamic ip address attached 498 DynamicIPRequired *bool `json:"dynamic_ip_required,omitempty"` 499 500 // PublicIP is the public IP address bound to the server 501 PublicAddress ScalewayIPAddress `json:"public_ip,omitempty"` 502 503 // State is the current status of the server 504 State string `json:"state,omitempty"` 505 506 // StateDetail is the detailed status of the server 507 StateDetail string `json:"state_detail,omitempty"` 508 509 // PrivateIP represents the private IPV4 attached to the server (changes on each boot) 510 PrivateIP string `json:"private_ip,omitempty"` 511 512 // Bootscript is the unique identifier of the selected bootscript 513 Bootscript *ScalewayBootscript `json:"bootscript,omitempty"` 514 515 // Hostname represents the ServerName in a format compatible with unix's hostname 516 Hostname string `json:"hostname,omitempty"` 517 518 // Tags represents user-defined tags 519 Tags []string `json:"tags,omitempty"` 520 521 // Volumes are the attached volumes 522 Volumes map[string]ScalewayVolume `json:"volumes,omitempty"` 523 524 // SecurityGroup is the selected security group object 525 SecurityGroup ScalewaySecurityGroup `json:"security_group,omitempty"` 526 527 // Organization is the owner of the server 528 Organization string `json:"organization,omitempty"` 529 530 // CommercialType is the commercial type of the server (i.e: C1, C2[SML], VC1S) 531 CommercialType string `json:"commercial_type,omitempty"` 532 533 // Location of the server 534 Location struct { 535 Platform string `json:"platform_id,omitempty"` 536 Chassis string `json:"chassis_id,omitempty"` 537 Cluster string `json:"cluster_id,omitempty"` 538 Hypervisor string `json:"hypervisor_id,omitempty"` 539 Blade string `json:"blade_id,omitempty"` 540 Node string `json:"node_id,omitempty"` 541 ZoneID string `json:"zone_id,omitempty"` 542 } `json:"location,omitempty"` 543 544 IPV6 *ScalewayIPV6Definition `json:"ipv6,omitempty"` 545 546 EnableIPV6 bool `json:"enable_ipv6,omitempty"` 547 548 // This fields are not returned by the API, we generate it 549 DNSPublic string `json:"dns_public,omitempty"` 550 DNSPrivate string `json:"dns_private,omitempty"` 551 } 552 553 // ScalewayIPV6Definition represents a Scaleway ipv6 554 type ScalewayIPV6Definition struct { 555 Netmask string `json:"netmask"` 556 Gateway string `json:"gateway"` 557 Address string `json:"address"` 558 } 559 560 // ScalewayServerPatchDefinition represents a Scaleway server with nullable fields (for PATCH) 561 type ScalewayServerPatchDefinition struct { 562 Arch *string `json:"arch,omitempty"` 563 Name *string `json:"name,omitempty"` 564 CreationDate *string `json:"creation_date,omitempty"` 565 ModificationDate *string `json:"modification_date,omitempty"` 566 Image *ScalewayImage `json:"image,omitempty"` 567 DynamicIPRequired *bool `json:"dynamic_ip_required,omitempty"` 568 PublicAddress *ScalewayIPAddress `json:"public_ip,omitempty"` 569 State *string `json:"state,omitempty"` 570 StateDetail *string `json:"state_detail,omitempty"` 571 PrivateIP *string `json:"private_ip,omitempty"` 572 Bootscript *string `json:"bootscript,omitempty"` 573 Hostname *string `json:"hostname,omitempty"` 574 Volumes *map[string]ScalewayVolume `json:"volumes,omitempty"` 575 SecurityGroup *ScalewaySecurityGroup `json:"security_group,omitempty"` 576 Organization *string `json:"organization,omitempty"` 577 Tags *[]string `json:"tags,omitempty"` 578 IPV6 *ScalewayIPV6Definition `json:"ipv6,omitempty"` 579 EnableIPV6 *bool `json:"enable_ipv6,omitempty"` 580 } 581 582 // ScalewayServerDefinition represents a Scaleway server with image definition 583 type ScalewayServerDefinition struct { 584 // Name is the user-defined name of the server 585 Name string `json:"name"` 586 587 // Image is the image used by the server 588 Image *string `json:"image,omitempty"` 589 590 // Volumes are the attached volumes 591 Volumes map[string]string `json:"volumes,omitempty"` 592 593 // DynamicIPRequired is a flag that defines a server with a dynamic ip address attached 594 DynamicIPRequired *bool `json:"dynamic_ip_required,omitempty"` 595 596 // Bootscript is the bootscript used by the server 597 Bootscript *string `json:"bootscript"` 598 599 // Tags are the metadata tags attached to the server 600 Tags []string `json:"tags,omitempty"` 601 602 // Organization is the owner of the server 603 Organization string `json:"organization"` 604 605 // CommercialType is the commercial type of the server (i.e: C1, C2[SML], VC1S) 606 CommercialType string `json:"commercial_type"` 607 608 PublicIP string `json:"public_ip,omitempty"` 609 610 EnableIPV6 bool `json:"enable_ipv6,omitempty"` 611 612 SecurityGroup string `json:"security_group,omitempty"` 613 } 614 615 // ScalewayOneServer represents the response of a GET /servers/UUID API call 616 type ScalewayOneServer struct { 617 Server ScalewayServer `json:"server,omitempty"` 618 } 619 620 // ScalewayServers represents a group of Scaleway servers 621 type ScalewayServers struct { 622 // Servers holds scaleway servers of the response 623 Servers []ScalewayServer `json:"servers,omitempty"` 624 } 625 626 // ScalewayServerAction represents an action to perform on a Scaleway server 627 type ScalewayServerAction struct { 628 // Action is the name of the action to trigger 629 Action string `json:"action,omitempty"` 630 } 631 632 // ScalewaySnapshotDefinition represents a Scaleway snapshot definition 633 type ScalewaySnapshotDefinition struct { 634 VolumeIDentifier string `json:"volume_id"` 635 Name string `json:"name,omitempty"` 636 Organization string `json:"organization"` 637 } 638 639 // ScalewayImageDefinition represents a Scaleway image definition 640 type ScalewayImageDefinition struct { 641 SnapshotIDentifier string `json:"root_volume"` 642 Name string `json:"name,omitempty"` 643 Organization string `json:"organization"` 644 Arch string `json:"arch"` 645 DefaultBootscript *string `json:"default_bootscript,omitempty"` 646 } 647 648 // ScalewayRoleDefinition represents a Scaleway Token UserId Role 649 type ScalewayRoleDefinition struct { 650 Organization ScalewayOrganizationDefinition `json:"organization,omitempty"` 651 Role string `json:"role,omitempty"` 652 } 653 654 // ScalewayTokenDefinition represents a Scaleway Token 655 type ScalewayTokenDefinition struct { 656 UserID string `json:"user_id"` 657 Description string `json:"description,omitempty"` 658 Roles ScalewayRoleDefinition `json:"roles"` 659 Expires string `json:"expires"` 660 InheritsUsersPerms bool `json:"inherits_user_perms"` 661 ID string `json:"id"` 662 } 663 664 // ScalewayTokensDefinition represents a Scaleway Tokens 665 type ScalewayTokensDefinition struct { 666 Token ScalewayTokenDefinition `json:"token"` 667 } 668 669 // ScalewayGetTokens represents a list of Scaleway Tokens 670 type ScalewayGetTokens struct { 671 Tokens []ScalewayTokenDefinition `json:"tokens"` 672 } 673 674 // ScalewayContainerData represents a Scaleway container data (S3) 675 type ScalewayContainerData struct { 676 LastModified string `json:"last_modified"` 677 Name string `json:"name"` 678 Size string `json:"size"` 679 } 680 681 // ScalewayGetContainerDatas represents a list of Scaleway containers data (S3) 682 type ScalewayGetContainerDatas struct { 683 Container []ScalewayContainerData `json:"container"` 684 } 685 686 // ScalewayContainer represents a Scaleway container (S3) 687 type ScalewayContainer struct { 688 ScalewayOrganizationDefinition `json:"organization"` 689 Name string `json:"name"` 690 Size string `json:"size"` 691 } 692 693 // ScalewayGetContainers represents a list of Scaleway containers (S3) 694 type ScalewayGetContainers struct { 695 Containers []ScalewayContainer `json:"containers"` 696 } 697 698 // ScalewayConnectResponse represents the answer from POST /tokens 699 type ScalewayConnectResponse struct { 700 Token ScalewayTokenDefinition `json:"token"` 701 } 702 703 // ScalewayConnect represents the data to connect 704 type ScalewayConnect struct { 705 Email string `json:"email"` 706 Password string `json:"password"` 707 Description string `json:"description"` 708 Expires bool `json:"expires"` 709 } 710 711 // ScalewayOrganizationDefinition represents a Scaleway Organization 712 type ScalewayOrganizationDefinition struct { 713 ID string `json:"id"` 714 Name string `json:"name"` 715 Users []ScalewayUserDefinition `json:"users"` 716 } 717 718 // ScalewayOrganizationsDefinition represents a Scaleway Organizations 719 type ScalewayOrganizationsDefinition struct { 720 Organizations []ScalewayOrganizationDefinition `json:"organizations"` 721 } 722 723 // ScalewayUserDefinition represents a Scaleway User 724 type ScalewayUserDefinition struct { 725 Email string `json:"email"` 726 Firstname string `json:"firstname"` 727 Fullname string `json:"fullname"` 728 ID string `json:"id"` 729 Lastname string `json:"lastname"` 730 Organizations []ScalewayOrganizationDefinition `json:"organizations"` 731 Roles []ScalewayRoleDefinition `json:"roles"` 732 SSHPublicKeys []ScalewayKeyDefinition `json:"ssh_public_keys"` 733 } 734 735 // ScalewayUsersDefinition represents the response of a GET /user 736 type ScalewayUsersDefinition struct { 737 User ScalewayUserDefinition `json:"user"` 738 } 739 740 // ScalewayKeyDefinition represents a key 741 type ScalewayKeyDefinition struct { 742 Key string `json:"key"` 743 Fingerprint string `json:"fingerprint,omitempty"` 744 } 745 746 // ScalewayUserPatchSSHKeyDefinition represents a User Patch 747 type ScalewayUserPatchSSHKeyDefinition struct { 748 SSHPublicKeys []ScalewayKeyDefinition `json:"ssh_public_keys"` 749 } 750 751 // ScalewayDashboardResp represents a dashboard received from the API 752 type ScalewayDashboardResp struct { 753 Dashboard ScalewayDashboard 754 } 755 756 // ScalewayDashboard represents a dashboard 757 type ScalewayDashboard struct { 758 VolumesCount int `json:"volumes_count"` 759 RunningServersCount int `json:"running_servers_count"` 760 ImagesCount int `json:"images_count"` 761 SnapshotsCount int `json:"snapshots_count"` 762 ServersCount int `json:"servers_count"` 763 IPsCount int `json:"ips_count"` 764 } 765 766 // ScalewayPermissions represents the response of GET /permissions 767 type ScalewayPermissions map[string]ScalewayPermCategory 768 769 // ScalewayPermCategory represents ScalewayPermissions's fields 770 type ScalewayPermCategory map[string][]string 771 772 // ScalewayPermissionDefinition represents the permissions 773 type ScalewayPermissionDefinition struct { 774 Permissions ScalewayPermissions `json:"permissions"` 775 } 776 777 // ScalewayUserdatas represents the response of a GET /user_data 778 type ScalewayUserdatas struct { 779 UserData []string `json:"user_data"` 780 } 781 782 // ScalewayQuota represents a map of quota (name, value) 783 type ScalewayQuota map[string]int 784 785 // ScalewayGetQuotas represents the response of GET /organizations/{orga_id}/quotas 786 type ScalewayGetQuotas struct { 787 Quotas ScalewayQuota `json:"quotas"` 788 } 789 790 // ScalewayUserdata represents []byte 791 type ScalewayUserdata []byte 792 793 // FuncMap used for json inspection 794 var FuncMap = template.FuncMap{ 795 "json": func(v interface{}) string { 796 a, _ := json.Marshal(v) 797 return string(a) 798 }, 799 } 800 801 // MarketLocalImageDefinition represents localImage of marketplace version 802 type MarketLocalImageDefinition struct { 803 Arch string `json:"arch"` 804 ID string `json:"id"` 805 Zone string `json:"zone"` 806 } 807 808 // MarketLocalImages represents an array of local images 809 type MarketLocalImages struct { 810 LocalImages []MarketLocalImageDefinition `json:"local_images"` 811 } 812 813 // MarketLocalImage represents local image 814 type MarketLocalImage struct { 815 LocalImages MarketLocalImageDefinition `json:"local_image"` 816 } 817 818 // MarketVersionDefinition represents version of marketplace image 819 type MarketVersionDefinition struct { 820 CreationDate string `json:"creation_date"` 821 ID string `json:"id"` 822 Image struct { 823 ID string `json:"id"` 824 Name string `json:"name"` 825 } `json:"image"` 826 ModificationDate string `json:"modification_date"` 827 Name string `json:"name"` 828 MarketLocalImages 829 } 830 831 // MarketVersions represents an array of marketplace image versions 832 type MarketVersions struct { 833 Versions []MarketVersionDefinition `json:"versions"` 834 } 835 836 // MarketVersion represents version of marketplace image 837 type MarketVersion struct { 838 Version MarketVersionDefinition `json:"version"` 839 } 840 841 // MarketImage represents MarketPlace image 842 type MarketImage struct { 843 Categories []string `json:"categories"` 844 CreationDate string `json:"creation_date"` 845 CurrentPublicVersion string `json:"current_public_version"` 846 Description string `json:"description"` 847 ID string `json:"id"` 848 Logo string `json:"logo"` 849 ModificationDate string `json:"modification_date"` 850 Name string `json:"name"` 851 Organization struct { 852 ID string `json:"id"` 853 Name string `json:"name"` 854 } `json:"organization"` 855 Public bool `json:"-"` 856 MarketVersions 857 } 858 859 // MarketImages represents MarketPlace images 860 type MarketImages struct { 861 Images []MarketImage `json:"images"` 862 } 863 864 // NewScalewayAPI creates a ready-to-use ScalewayAPI client 865 func NewScalewayAPI(organization, token, userAgent, region string, options ...func(*ScalewayAPI)) (*ScalewayAPI, error) { 866 s := &ScalewayAPI{ 867 // exposed 868 Organization: organization, 869 Token: token, 870 Logger: NewDefaultLogger(), 871 872 // internal 873 client: &http.Client{}, 874 verbose: os.Getenv("SCW_VERBOSE_API") != "", 875 password: "", 876 userAgent: userAgent, 877 } 878 for _, option := range options { 879 option(s) 880 } 881 cache, err := NewScalewayCache(func() { s.Logger.Debugf("Writing cache file to disk") }) 882 if err != nil { 883 return nil, err 884 } 885 s.Cache = cache 886 if os.Getenv("SCW_TLSVERIFY") == "0" { 887 s.client.Transport = &http.Transport{ 888 TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 889 } 890 } 891 switch region { 892 case "par1", "": 893 s.computeAPI = ComputeAPIPar1 894 case "ams1": 895 s.computeAPI = ComputeAPIAms1 896 default: 897 return nil, fmt.Errorf("%s isn't a valid region", region) 898 } 899 s.Region = region 900 if url := os.Getenv("SCW_COMPUTE_API"); url != "" { 901 s.computeAPI = url 902 } 903 return s, nil 904 } 905 906 // ClearCache clears the cache 907 func (s *ScalewayAPI) ClearCache() { 908 s.Cache.Clear() 909 } 910 911 // Sync flushes out the cache to the disk 912 func (s *ScalewayAPI) Sync() { 913 s.Cache.Save() 914 } 915 916 func (s *ScalewayAPI) response(method, uri string, content io.Reader) (resp *http.Response, err error) { 917 var ( 918 req *http.Request 919 ) 920 921 req, err = http.NewRequest(method, uri, content) 922 if err != nil { 923 err = fmt.Errorf("response %s %s", method, uri) 924 return 925 } 926 req.Header.Set("X-Auth-Token", s.Token) 927 req.Header.Set("Content-Type", "application/json") 928 req.Header.Set("User-Agent", s.userAgent) 929 s.LogHTTP(req) 930 if s.verbose { 931 dump, _ := httputil.DumpRequest(req, true) 932 s.Debugf("%v", string(dump)) 933 } else { 934 s.Debugf("[%s]: %v", method, uri) 935 } 936 resp, err = s.client.Do(req) 937 return 938 } 939 940 // GetResponsePaginate fetchs all resources and returns an http.Response object for the requested resource 941 func (s *ScalewayAPI) GetResponsePaginate(apiURL, resource string, values url.Values) (*http.Response, error) { 942 resp, err := s.response("HEAD", fmt.Sprintf("%s/%s?%s", strings.TrimRight(apiURL, "/"), resource, values.Encode()), nil) 943 if err != nil { 944 return nil, err 945 } 946 947 count := resp.Header.Get("X-Total-Count") 948 var maxElem int 949 if count == "" { 950 maxElem = 0 951 } else { 952 maxElem, err = strconv.Atoi(count) 953 if err != nil { 954 return nil, err 955 } 956 } 957 958 get := maxElem / perPage 959 if (float32(maxElem) / perPage) > float32(get) { 960 get++ 961 } 962 963 if get <= 1 { // If there is 0 or 1 page of result, the response is not paginated 964 if len(values) == 0 { 965 return s.response("GET", fmt.Sprintf("%s/%s", strings.TrimRight(apiURL, "/"), resource), nil) 966 } 967 return s.response("GET", fmt.Sprintf("%s/%s?%s", strings.TrimRight(apiURL, "/"), resource, values.Encode()), nil) 968 } 969 970 fetchAll := !(values.Get("per_page") != "" || values.Get("page") != "") 971 if fetchAll { 972 var g errgroup.Group 973 974 ch := make(chan *http.Response, get) 975 for i := 1; i <= get; i++ { 976 i := i // closure tricks 977 g.Go(func() (err error) { 978 var resp *http.Response 979 980 val := url.Values{} 981 val.Set("per_page", fmt.Sprintf("%v", perPage)) 982 val.Set("page", fmt.Sprintf("%v", i)) 983 resp, err = s.response("GET", fmt.Sprintf("%s/%s?%s", strings.TrimRight(apiURL, "/"), resource, val.Encode()), nil) 984 ch <- resp 985 return 986 }) 987 } 988 if err = g.Wait(); err != nil { 989 return nil, err 990 } 991 newBody := make(map[string][]json.RawMessage) 992 body := make(map[string][]json.RawMessage) 993 key := "" 994 for i := 0; i < get; i++ { 995 res := <-ch 996 if res.StatusCode != http.StatusOK { 997 return res, nil 998 } 999 content, err := ioutil.ReadAll(res.Body) 1000 res.Body.Close() 1001 if err != nil { 1002 return nil, err 1003 } 1004 if err := json.Unmarshal(content, &body); err != nil { 1005 return nil, err 1006 } 1007 1008 if i == 0 { 1009 resp = res 1010 for k := range body { 1011 key = k 1012 break 1013 } 1014 } 1015 newBody[key] = append(newBody[key], body[key]...) 1016 } 1017 payload := new(bytes.Buffer) 1018 if err := json.NewEncoder(payload).Encode(newBody); err != nil { 1019 return nil, err 1020 } 1021 resp.Body = ioutil.NopCloser(payload) 1022 } else { 1023 resp, err = s.response("GET", fmt.Sprintf("%s/%s?%s", strings.TrimRight(apiURL, "/"), resource, values.Encode()), nil) 1024 } 1025 return resp, err 1026 } 1027 1028 // PostResponse returns an http.Response object for the updated resource 1029 func (s *ScalewayAPI) PostResponse(apiURL, resource string, data interface{}) (*http.Response, error) { 1030 payload := new(bytes.Buffer) 1031 if err := json.NewEncoder(payload).Encode(data); err != nil { 1032 return nil, err 1033 } 1034 return s.response("POST", fmt.Sprintf("%s/%s", strings.TrimRight(apiURL, "/"), resource), payload) 1035 } 1036 1037 // PatchResponse returns an http.Response object for the updated resource 1038 func (s *ScalewayAPI) PatchResponse(apiURL, resource string, data interface{}) (*http.Response, error) { 1039 payload := new(bytes.Buffer) 1040 if err := json.NewEncoder(payload).Encode(data); err != nil { 1041 return nil, err 1042 } 1043 return s.response("PATCH", fmt.Sprintf("%s/%s", strings.TrimRight(apiURL, "/"), resource), payload) 1044 } 1045 1046 // PutResponse returns an http.Response object for the updated resource 1047 func (s *ScalewayAPI) PutResponse(apiURL, resource string, data interface{}) (*http.Response, error) { 1048 payload := new(bytes.Buffer) 1049 if err := json.NewEncoder(payload).Encode(data); err != nil { 1050 return nil, err 1051 } 1052 return s.response("PUT", fmt.Sprintf("%s/%s", strings.TrimRight(apiURL, "/"), resource), payload) 1053 } 1054 1055 // DeleteResponse returns an http.Response object for the deleted resource 1056 func (s *ScalewayAPI) DeleteResponse(apiURL, resource string) (*http.Response, error) { 1057 return s.response("DELETE", fmt.Sprintf("%s/%s", strings.TrimRight(apiURL, "/"), resource), nil) 1058 } 1059 1060 // handleHTTPError checks the statusCode and displays the error 1061 func (s *ScalewayAPI) handleHTTPError(goodStatusCode []int, resp *http.Response) ([]byte, error) { 1062 body, err := ioutil.ReadAll(resp.Body) 1063 if err != nil { 1064 return nil, err 1065 } 1066 if s.verbose { 1067 resp.Body = ioutil.NopCloser(bytes.NewBuffer(body)) 1068 dump, err := httputil.DumpResponse(resp, true) 1069 if err == nil { 1070 var js bytes.Buffer 1071 1072 err = json.Indent(&js, body, "", " ") 1073 if err != nil { 1074 s.Debugf("[Response]: [%v]\n%v", resp.StatusCode, string(dump)) 1075 } else { 1076 s.Debugf("[Response]: [%v]\n%v", resp.StatusCode, js.String()) 1077 } 1078 } 1079 } else { 1080 s.Debugf("[Response]: [%v]\n%v", resp.StatusCode, string(body)) 1081 } 1082 1083 if resp.StatusCode >= http.StatusInternalServerError { 1084 return nil, errors.New(string(body)) 1085 } 1086 good := false 1087 for _, code := range goodStatusCode { 1088 if code == resp.StatusCode { 1089 good = true 1090 } 1091 } 1092 if !good { 1093 var scwError ScalewayAPIError 1094 1095 if err := json.Unmarshal(body, &scwError); err != nil { 1096 return nil, err 1097 } 1098 scwError.StatusCode = resp.StatusCode 1099 s.Debugf("%s", scwError.Error()) 1100 return nil, scwError 1101 } 1102 return body, nil 1103 } 1104 1105 func (s *ScalewayAPI) fetchServers(api string, query url.Values, out chan<- ScalewayServers) func() error { 1106 return func() error { 1107 resp, err := s.GetResponsePaginate(api, "servers", query) 1108 if resp != nil { 1109 defer resp.Body.Close() 1110 } 1111 if err != nil { 1112 return err 1113 } 1114 1115 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 1116 if err != nil { 1117 return err 1118 } 1119 var servers ScalewayServers 1120 1121 if err = json.Unmarshal(body, &servers); err != nil { 1122 return err 1123 } 1124 out <- servers 1125 return nil 1126 } 1127 } 1128 1129 // GetServers gets the list of servers from the ScalewayAPI 1130 func (s *ScalewayAPI) GetServers(all bool, limit int) (*[]ScalewayServer, error) { 1131 query := url.Values{} 1132 if !all { 1133 query.Set("state", "running") 1134 } 1135 if limit > 0 { 1136 // FIXME: wait for the API to be ready 1137 // query.Set("per_page", strconv.Itoa(limit)) 1138 panic("Not implemented yet") 1139 } 1140 if all && limit == 0 { 1141 s.Cache.ClearServers() 1142 } 1143 var ( 1144 g errgroup.Group 1145 apis = []string{ 1146 ComputeAPIPar1, 1147 ComputeAPIAms1, 1148 } 1149 ) 1150 1151 serverChan := make(chan ScalewayServers, 2) 1152 for _, api := range apis { 1153 g.Go(s.fetchServers(api, query, serverChan)) 1154 } 1155 1156 if err := g.Wait(); err != nil { 1157 return nil, err 1158 } 1159 close(serverChan) 1160 var servers ScalewayServers 1161 1162 for server := range serverChan { 1163 servers.Servers = append(servers.Servers, server.Servers...) 1164 } 1165 1166 for i, server := range servers.Servers { 1167 servers.Servers[i].DNSPublic = server.Identifier + URLPublicDNS 1168 servers.Servers[i].DNSPrivate = server.Identifier + URLPrivateDNS 1169 s.Cache.InsertServer(server.Identifier, server.Location.ZoneID, server.Arch, server.Organization, server.Name) 1170 } 1171 return &servers.Servers, nil 1172 } 1173 1174 // ScalewaySortServers represents a wrapper to sort by CreationDate the servers 1175 type ScalewaySortServers []ScalewayServer 1176 1177 func (s ScalewaySortServers) Len() int { 1178 return len(s) 1179 } 1180 1181 func (s ScalewaySortServers) Swap(i, j int) { 1182 s[i], s[j] = s[j], s[i] 1183 } 1184 1185 func (s ScalewaySortServers) Less(i, j int) bool { 1186 date1, _ := time.Parse("2006-01-02T15:04:05.000000+00:00", s[i].CreationDate) 1187 date2, _ := time.Parse("2006-01-02T15:04:05.000000+00:00", s[j].CreationDate) 1188 return date2.Before(date1) 1189 } 1190 1191 // GetServer gets a server from the ScalewayAPI 1192 func (s *ScalewayAPI) GetServer(serverID string) (*ScalewayServer, error) { 1193 resp, err := s.GetResponsePaginate(s.computeAPI, "servers/"+serverID, url.Values{}) 1194 if resp != nil { 1195 defer resp.Body.Close() 1196 } 1197 if err != nil { 1198 return nil, err 1199 } 1200 1201 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 1202 if err != nil { 1203 return nil, err 1204 } 1205 1206 var oneServer ScalewayOneServer 1207 1208 if err = json.Unmarshal(body, &oneServer); err != nil { 1209 return nil, err 1210 } 1211 // FIXME arch, owner, title 1212 oneServer.Server.DNSPublic = oneServer.Server.Identifier + URLPublicDNS 1213 oneServer.Server.DNSPrivate = oneServer.Server.Identifier + URLPrivateDNS 1214 s.Cache.InsertServer(oneServer.Server.Identifier, oneServer.Server.Location.ZoneID, oneServer.Server.Arch, oneServer.Server.Organization, oneServer.Server.Name) 1215 return &oneServer.Server, nil 1216 } 1217 1218 // PostServerAction posts an action on a server 1219 func (s *ScalewayAPI) PostServerAction(serverID, action string) error { 1220 data := ScalewayServerAction{ 1221 Action: action, 1222 } 1223 resp, err := s.PostResponse(s.computeAPI, fmt.Sprintf("servers/%s/action", serverID), data) 1224 if resp != nil { 1225 defer resp.Body.Close() 1226 } 1227 if err != nil { 1228 return err 1229 } 1230 1231 _, err = s.handleHTTPError([]int{http.StatusAccepted}, resp) 1232 return err 1233 } 1234 1235 // DeleteServer deletes a server 1236 func (s *ScalewayAPI) DeleteServer(serverID string) error { 1237 defer s.Cache.RemoveServer(serverID) 1238 resp, err := s.DeleteResponse(s.computeAPI, fmt.Sprintf("servers/%s", serverID)) 1239 if resp != nil { 1240 defer resp.Body.Close() 1241 } 1242 if err != nil { 1243 return err 1244 } 1245 1246 if _, err = s.handleHTTPError([]int{http.StatusNoContent}, resp); err != nil { 1247 return err 1248 } 1249 return nil 1250 } 1251 1252 // PostServer creates a new server 1253 func (s *ScalewayAPI) PostServer(definition ScalewayServerDefinition) (string, error) { 1254 definition.Organization = s.Organization 1255 1256 resp, err := s.PostResponse(s.computeAPI, "servers", definition) 1257 if resp != nil { 1258 defer resp.Body.Close() 1259 } 1260 if err != nil { 1261 return "", err 1262 } 1263 1264 body, err := s.handleHTTPError([]int{http.StatusCreated}, resp) 1265 if err != nil { 1266 return "", err 1267 } 1268 var server ScalewayOneServer 1269 1270 if err = json.Unmarshal(body, &server); err != nil { 1271 return "", err 1272 } 1273 // FIXME arch, owner, title 1274 s.Cache.InsertServer(server.Server.Identifier, server.Server.Location.ZoneID, server.Server.Arch, server.Server.Organization, server.Server.Name) 1275 return server.Server.Identifier, nil 1276 } 1277 1278 // PatchUserSSHKey updates a user 1279 func (s *ScalewayAPI) PatchUserSSHKey(UserID string, definition ScalewayUserPatchSSHKeyDefinition) error { 1280 resp, err := s.PatchResponse(AccountAPI, fmt.Sprintf("users/%s", UserID), definition) 1281 if resp != nil { 1282 defer resp.Body.Close() 1283 } 1284 if err != nil { 1285 return err 1286 } 1287 if _, err := s.handleHTTPError([]int{http.StatusOK}, resp); err != nil { 1288 return err 1289 } 1290 return nil 1291 } 1292 1293 // PatchServer updates a server 1294 func (s *ScalewayAPI) PatchServer(serverID string, definition ScalewayServerPatchDefinition) error { 1295 resp, err := s.PatchResponse(s.computeAPI, fmt.Sprintf("servers/%s", serverID), definition) 1296 if resp != nil { 1297 defer resp.Body.Close() 1298 } 1299 if err != nil { 1300 return err 1301 } 1302 1303 if _, err := s.handleHTTPError([]int{http.StatusOK}, resp); err != nil { 1304 return err 1305 } 1306 return nil 1307 } 1308 1309 // PostSnapshot creates a new snapshot 1310 func (s *ScalewayAPI) PostSnapshot(volumeID string, name string) (string, error) { 1311 definition := ScalewaySnapshotDefinition{ 1312 VolumeIDentifier: volumeID, 1313 Name: name, 1314 Organization: s.Organization, 1315 } 1316 resp, err := s.PostResponse(s.computeAPI, "snapshots", definition) 1317 if resp != nil { 1318 defer resp.Body.Close() 1319 } 1320 if err != nil { 1321 return "", err 1322 } 1323 1324 body, err := s.handleHTTPError([]int{http.StatusCreated}, resp) 1325 if err != nil { 1326 return "", err 1327 } 1328 var snapshot ScalewayOneSnapshot 1329 1330 if err = json.Unmarshal(body, &snapshot); err != nil { 1331 return "", err 1332 } 1333 // FIXME arch, owner, title 1334 s.Cache.InsertSnapshot(snapshot.Snapshot.Identifier, "", "", snapshot.Snapshot.Organization, snapshot.Snapshot.Name) 1335 return snapshot.Snapshot.Identifier, nil 1336 } 1337 1338 // PostImage creates a new image 1339 func (s *ScalewayAPI) PostImage(volumeID string, name string, bootscript string, arch string) (string, error) { 1340 definition := ScalewayImageDefinition{ 1341 SnapshotIDentifier: volumeID, 1342 Name: name, 1343 Organization: s.Organization, 1344 Arch: arch, 1345 } 1346 if bootscript != "" { 1347 definition.DefaultBootscript = &bootscript 1348 } 1349 1350 resp, err := s.PostResponse(s.computeAPI, "images", definition) 1351 if resp != nil { 1352 defer resp.Body.Close() 1353 } 1354 if err != nil { 1355 return "", err 1356 } 1357 1358 body, err := s.handleHTTPError([]int{http.StatusCreated}, resp) 1359 if err != nil { 1360 return "", err 1361 } 1362 var image ScalewayOneImage 1363 1364 if err = json.Unmarshal(body, &image); err != nil { 1365 return "", err 1366 } 1367 // FIXME region, arch, owner, title 1368 s.Cache.InsertImage(image.Image.Identifier, "", image.Image.Arch, image.Image.Organization, image.Image.Name, "") 1369 return image.Image.Identifier, nil 1370 } 1371 1372 // PostVolume creates a new volume 1373 func (s *ScalewayAPI) PostVolume(definition ScalewayVolumeDefinition) (string, error) { 1374 definition.Organization = s.Organization 1375 if definition.Type == "" { 1376 definition.Type = "l_ssd" 1377 } 1378 1379 resp, err := s.PostResponse(s.computeAPI, "volumes", definition) 1380 if resp != nil { 1381 defer resp.Body.Close() 1382 } 1383 if err != nil { 1384 return "", err 1385 } 1386 1387 body, err := s.handleHTTPError([]int{http.StatusCreated}, resp) 1388 if err != nil { 1389 return "", err 1390 } 1391 var volume ScalewayOneVolume 1392 1393 if err = json.Unmarshal(body, &volume); err != nil { 1394 return "", err 1395 } 1396 // FIXME: s.Cache.InsertVolume(volume.Volume.Identifier, volume.Volume.Name) 1397 return volume.Volume.Identifier, nil 1398 } 1399 1400 // PutVolume updates a volume 1401 func (s *ScalewayAPI) PutVolume(volumeID string, definition ScalewayVolumePutDefinition) error { 1402 resp, err := s.PutResponse(s.computeAPI, fmt.Sprintf("volumes/%s", volumeID), definition) 1403 if resp != nil { 1404 defer resp.Body.Close() 1405 } 1406 if err != nil { 1407 return err 1408 } 1409 1410 _, err = s.handleHTTPError([]int{http.StatusOK}, resp) 1411 return err 1412 } 1413 1414 // ResolveServer attempts to find a matching Identifier for the input string 1415 func (s *ScalewayAPI) ResolveServer(needle string) (ScalewayResolverResults, error) { 1416 servers, err := s.Cache.LookUpServers(needle, true) 1417 if err != nil { 1418 return servers, err 1419 } 1420 if len(servers) == 0 { 1421 if _, err = s.GetServers(true, 0); err != nil { 1422 return nil, err 1423 } 1424 servers, err = s.Cache.LookUpServers(needle, true) 1425 } 1426 return servers, err 1427 } 1428 1429 // ResolveVolume attempts to find a matching Identifier for the input string 1430 func (s *ScalewayAPI) ResolveVolume(needle string) (ScalewayResolverResults, error) { 1431 volumes, err := s.Cache.LookUpVolumes(needle, true) 1432 if err != nil { 1433 return volumes, err 1434 } 1435 if len(volumes) == 0 { 1436 if _, err = s.GetVolumes(); err != nil { 1437 return nil, err 1438 } 1439 volumes, err = s.Cache.LookUpVolumes(needle, true) 1440 } 1441 return volumes, err 1442 } 1443 1444 // ResolveSnapshot attempts to find a matching Identifier for the input string 1445 func (s *ScalewayAPI) ResolveSnapshot(needle string) (ScalewayResolverResults, error) { 1446 snapshots, err := s.Cache.LookUpSnapshots(needle, true) 1447 if err != nil { 1448 return snapshots, err 1449 } 1450 if len(snapshots) == 0 { 1451 if _, err = s.GetSnapshots(); err != nil { 1452 return nil, err 1453 } 1454 snapshots, err = s.Cache.LookUpSnapshots(needle, true) 1455 } 1456 return snapshots, err 1457 } 1458 1459 // ResolveImage attempts to find a matching Identifier for the input string 1460 func (s *ScalewayAPI) ResolveImage(needle string) (ScalewayResolverResults, error) { 1461 images, err := s.Cache.LookUpImages(needle, true) 1462 if err != nil { 1463 return images, err 1464 } 1465 if len(images) == 0 { 1466 if _, err = s.GetImages(); err != nil { 1467 return nil, err 1468 } 1469 images, err = s.Cache.LookUpImages(needle, true) 1470 } 1471 return images, err 1472 } 1473 1474 // ResolveBootscript attempts to find a matching Identifier for the input string 1475 func (s *ScalewayAPI) ResolveBootscript(needle string) (ScalewayResolverResults, error) { 1476 bootscripts, err := s.Cache.LookUpBootscripts(needle, true) 1477 if err != nil { 1478 return bootscripts, err 1479 } 1480 if len(bootscripts) == 0 { 1481 if _, err = s.GetBootscripts(); err != nil { 1482 return nil, err 1483 } 1484 bootscripts, err = s.Cache.LookUpBootscripts(needle, true) 1485 } 1486 return bootscripts, err 1487 } 1488 1489 // GetImages gets the list of images from the ScalewayAPI 1490 func (s *ScalewayAPI) GetImages() (*[]MarketImage, error) { 1491 images, err := s.GetMarketPlaceImages("") 1492 if err != nil { 1493 return nil, err 1494 } 1495 s.Cache.ClearImages() 1496 for i, image := range images.Images { 1497 if image.CurrentPublicVersion != "" { 1498 for _, version := range image.Versions { 1499 if version.ID == image.CurrentPublicVersion { 1500 for _, localImage := range version.LocalImages { 1501 images.Images[i].Public = true 1502 s.Cache.InsertImage(localImage.ID, localImage.Zone, localImage.Arch, image.Organization.ID, image.Name, image.CurrentPublicVersion) 1503 } 1504 } 1505 } 1506 } 1507 } 1508 values := url.Values{} 1509 values.Set("organization", s.Organization) 1510 resp, err := s.GetResponsePaginate(s.computeAPI, "images", values) 1511 if resp != nil { 1512 defer resp.Body.Close() 1513 } 1514 if err != nil { 1515 return nil, err 1516 } 1517 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 1518 if err != nil { 1519 return nil, err 1520 } 1521 var OrgaImages ScalewayImages 1522 1523 if err = json.Unmarshal(body, &OrgaImages); err != nil { 1524 return nil, err 1525 } 1526 1527 for _, orgaImage := range OrgaImages.Images { 1528 images.Images = append(images.Images, MarketImage{ 1529 Categories: []string{"MyImages"}, 1530 CreationDate: orgaImage.CreationDate, 1531 CurrentPublicVersion: orgaImage.Identifier, 1532 ModificationDate: orgaImage.ModificationDate, 1533 Name: orgaImage.Name, 1534 Public: false, 1535 MarketVersions: MarketVersions{ 1536 Versions: []MarketVersionDefinition{ 1537 { 1538 CreationDate: orgaImage.CreationDate, 1539 ID: orgaImage.Identifier, 1540 ModificationDate: orgaImage.ModificationDate, 1541 MarketLocalImages: MarketLocalImages{ 1542 LocalImages: []MarketLocalImageDefinition{ 1543 { 1544 Arch: orgaImage.Arch, 1545 ID: orgaImage.Identifier, 1546 // TODO: fecth images from ams1 and par1 1547 Zone: s.Region, 1548 }, 1549 }, 1550 }, 1551 }, 1552 }, 1553 }, 1554 }) 1555 s.Cache.InsertImage(orgaImage.Identifier, s.Region, orgaImage.Arch, orgaImage.Organization, orgaImage.Name, "") 1556 } 1557 return &images.Images, nil 1558 } 1559 1560 // GetImage gets an image from the ScalewayAPI 1561 func (s *ScalewayAPI) GetImage(imageID string) (*ScalewayImage, error) { 1562 resp, err := s.GetResponsePaginate(s.computeAPI, "images/"+imageID, url.Values{}) 1563 if resp != nil { 1564 defer resp.Body.Close() 1565 } 1566 if err != nil { 1567 return nil, err 1568 } 1569 1570 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 1571 if err != nil { 1572 return nil, err 1573 } 1574 var oneImage ScalewayOneImage 1575 1576 if err = json.Unmarshal(body, &oneImage); err != nil { 1577 return nil, err 1578 } 1579 // FIXME owner, title 1580 s.Cache.InsertImage(oneImage.Image.Identifier, s.Region, oneImage.Image.Arch, oneImage.Image.Organization, oneImage.Image.Name, "") 1581 return &oneImage.Image, nil 1582 } 1583 1584 // DeleteImage deletes a image 1585 func (s *ScalewayAPI) DeleteImage(imageID string) error { 1586 defer s.Cache.RemoveImage(imageID) 1587 resp, err := s.DeleteResponse(s.computeAPI, fmt.Sprintf("images/%s", imageID)) 1588 if resp != nil { 1589 defer resp.Body.Close() 1590 } 1591 if err != nil { 1592 return err 1593 } 1594 1595 if _, err := s.handleHTTPError([]int{http.StatusNoContent}, resp); err != nil { 1596 return err 1597 } 1598 return nil 1599 } 1600 1601 // DeleteSnapshot deletes a snapshot 1602 func (s *ScalewayAPI) DeleteSnapshot(snapshotID string) error { 1603 defer s.Cache.RemoveSnapshot(snapshotID) 1604 resp, err := s.DeleteResponse(s.computeAPI, fmt.Sprintf("snapshots/%s", snapshotID)) 1605 if resp != nil { 1606 defer resp.Body.Close() 1607 } 1608 if err != nil { 1609 return err 1610 } 1611 1612 if _, err := s.handleHTTPError([]int{http.StatusNoContent}, resp); err != nil { 1613 return err 1614 } 1615 return nil 1616 } 1617 1618 // DeleteVolume deletes a volume 1619 func (s *ScalewayAPI) DeleteVolume(volumeID string) error { 1620 defer s.Cache.RemoveVolume(volumeID) 1621 resp, err := s.DeleteResponse(s.computeAPI, fmt.Sprintf("volumes/%s", volumeID)) 1622 if resp != nil { 1623 defer resp.Body.Close() 1624 } 1625 if err != nil { 1626 return err 1627 } 1628 1629 if _, err := s.handleHTTPError([]int{http.StatusNoContent}, resp); err != nil { 1630 return err 1631 } 1632 return nil 1633 } 1634 1635 // GetSnapshots gets the list of snapshots from the ScalewayAPI 1636 func (s *ScalewayAPI) GetSnapshots() (*[]ScalewaySnapshot, error) { 1637 query := url.Values{} 1638 s.Cache.ClearSnapshots() 1639 1640 resp, err := s.GetResponsePaginate(s.computeAPI, "snapshots", query) 1641 if resp != nil { 1642 defer resp.Body.Close() 1643 } 1644 if err != nil { 1645 return nil, err 1646 } 1647 1648 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 1649 if err != nil { 1650 return nil, err 1651 } 1652 var snapshots ScalewaySnapshots 1653 1654 if err = json.Unmarshal(body, &snapshots); err != nil { 1655 return nil, err 1656 } 1657 for _, snapshot := range snapshots.Snapshots { 1658 // FIXME region, arch, owner, title 1659 s.Cache.InsertSnapshot(snapshot.Identifier, "", "", snapshot.Organization, snapshot.Name) 1660 } 1661 return &snapshots.Snapshots, nil 1662 } 1663 1664 // GetSnapshot gets a snapshot from the ScalewayAPI 1665 func (s *ScalewayAPI) GetSnapshot(snapshotID string) (*ScalewaySnapshot, error) { 1666 resp, err := s.GetResponsePaginate(s.computeAPI, "snapshots/"+snapshotID, url.Values{}) 1667 if resp != nil { 1668 defer resp.Body.Close() 1669 } 1670 if err != nil { 1671 return nil, err 1672 } 1673 1674 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 1675 if err != nil { 1676 return nil, err 1677 } 1678 var oneSnapshot ScalewayOneSnapshot 1679 1680 if err = json.Unmarshal(body, &oneSnapshot); err != nil { 1681 return nil, err 1682 } 1683 // FIXME region, arch, owner, title 1684 s.Cache.InsertSnapshot(oneSnapshot.Snapshot.Identifier, "", "", oneSnapshot.Snapshot.Organization, oneSnapshot.Snapshot.Name) 1685 return &oneSnapshot.Snapshot, nil 1686 } 1687 1688 // GetVolumes gets the list of volumes from the ScalewayAPI 1689 func (s *ScalewayAPI) GetVolumes() (*[]ScalewayVolume, error) { 1690 query := url.Values{} 1691 s.Cache.ClearVolumes() 1692 1693 resp, err := s.GetResponsePaginate(s.computeAPI, "volumes", query) 1694 if resp != nil { 1695 defer resp.Body.Close() 1696 } 1697 if err != nil { 1698 return nil, err 1699 } 1700 1701 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 1702 if err != nil { 1703 return nil, err 1704 } 1705 1706 var volumes ScalewayVolumes 1707 1708 if err = json.Unmarshal(body, &volumes); err != nil { 1709 return nil, err 1710 } 1711 for _, volume := range volumes.Volumes { 1712 // FIXME region, arch, owner, title 1713 s.Cache.InsertVolume(volume.Identifier, "", "", volume.Organization, volume.Name) 1714 } 1715 return &volumes.Volumes, nil 1716 } 1717 1718 // GetVolume gets a volume from the ScalewayAPI 1719 func (s *ScalewayAPI) GetVolume(volumeID string) (*ScalewayVolume, error) { 1720 resp, err := s.GetResponsePaginate(s.computeAPI, "volumes/"+volumeID, url.Values{}) 1721 if resp != nil { 1722 defer resp.Body.Close() 1723 } 1724 if err != nil { 1725 return nil, err 1726 } 1727 1728 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 1729 if err != nil { 1730 return nil, err 1731 } 1732 var oneVolume ScalewayOneVolume 1733 1734 if err = json.Unmarshal(body, &oneVolume); err != nil { 1735 return nil, err 1736 } 1737 // FIXME region, arch, owner, title 1738 s.Cache.InsertVolume(oneVolume.Volume.Identifier, "", "", oneVolume.Volume.Organization, oneVolume.Volume.Name) 1739 return &oneVolume.Volume, nil 1740 } 1741 1742 // GetBootscripts gets the list of bootscripts from the ScalewayAPI 1743 func (s *ScalewayAPI) GetBootscripts() (*[]ScalewayBootscript, error) { 1744 query := url.Values{} 1745 1746 s.Cache.ClearBootscripts() 1747 resp, err := s.GetResponsePaginate(s.computeAPI, "bootscripts", query) 1748 if resp != nil { 1749 defer resp.Body.Close() 1750 } 1751 if err != nil { 1752 return nil, err 1753 } 1754 1755 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 1756 if err != nil { 1757 return nil, err 1758 } 1759 var bootscripts ScalewayBootscripts 1760 1761 if err = json.Unmarshal(body, &bootscripts); err != nil { 1762 return nil, err 1763 } 1764 for _, bootscript := range bootscripts.Bootscripts { 1765 // FIXME region, arch, owner, title 1766 s.Cache.InsertBootscript(bootscript.Identifier, "", bootscript.Arch, bootscript.Organization, bootscript.Title) 1767 } 1768 return &bootscripts.Bootscripts, nil 1769 } 1770 1771 // GetBootscript gets a bootscript from the ScalewayAPI 1772 func (s *ScalewayAPI) GetBootscript(bootscriptID string) (*ScalewayBootscript, error) { 1773 resp, err := s.GetResponsePaginate(s.computeAPI, "bootscripts/"+bootscriptID, url.Values{}) 1774 if resp != nil { 1775 defer resp.Body.Close() 1776 } 1777 if err != nil { 1778 return nil, err 1779 } 1780 1781 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 1782 if err != nil { 1783 return nil, err 1784 } 1785 var oneBootscript ScalewayOneBootscript 1786 1787 if err = json.Unmarshal(body, &oneBootscript); err != nil { 1788 return nil, err 1789 } 1790 // FIXME region, arch, owner, title 1791 s.Cache.InsertBootscript(oneBootscript.Bootscript.Identifier, "", oneBootscript.Bootscript.Arch, oneBootscript.Bootscript.Organization, oneBootscript.Bootscript.Title) 1792 return &oneBootscript.Bootscript, nil 1793 } 1794 1795 // GetUserdatas gets list of userdata for a server 1796 func (s *ScalewayAPI) GetUserdatas(serverID string, metadata bool) (*ScalewayUserdatas, error) { 1797 var uri, endpoint string 1798 1799 endpoint = s.computeAPI 1800 if metadata { 1801 uri = "/user_data" 1802 endpoint = MetadataAPI 1803 } else { 1804 uri = fmt.Sprintf("servers/%s/user_data", serverID) 1805 } 1806 1807 resp, err := s.GetResponsePaginate(endpoint, uri, url.Values{}) 1808 if resp != nil { 1809 defer resp.Body.Close() 1810 } 1811 if err != nil { 1812 return nil, err 1813 } 1814 1815 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 1816 if err != nil { 1817 return nil, err 1818 } 1819 var userdatas ScalewayUserdatas 1820 1821 if err = json.Unmarshal(body, &userdatas); err != nil { 1822 return nil, err 1823 } 1824 return &userdatas, nil 1825 } 1826 1827 func (s *ScalewayUserdata) String() string { 1828 return string(*s) 1829 } 1830 1831 // GetUserdata gets a specific userdata for a server 1832 func (s *ScalewayAPI) GetUserdata(serverID, key string, metadata bool) (*ScalewayUserdata, error) { 1833 var uri, endpoint string 1834 1835 endpoint = s.computeAPI 1836 if metadata { 1837 uri = fmt.Sprintf("/user_data/%s", key) 1838 endpoint = MetadataAPI 1839 } else { 1840 uri = fmt.Sprintf("servers/%s/user_data/%s", serverID, key) 1841 } 1842 1843 var err error 1844 resp, err := s.GetResponsePaginate(endpoint, uri, url.Values{}) 1845 if resp != nil { 1846 defer resp.Body.Close() 1847 } 1848 if err != nil { 1849 return nil, err 1850 } 1851 1852 if resp.StatusCode != http.StatusOK { 1853 return nil, fmt.Errorf("no such user_data %q (%d)", key, resp.StatusCode) 1854 } 1855 var data ScalewayUserdata 1856 data, err = ioutil.ReadAll(resp.Body) 1857 return &data, err 1858 } 1859 1860 // PatchUserdata sets a user data 1861 func (s *ScalewayAPI) PatchUserdata(serverID, key string, value []byte, metadata bool) error { 1862 var resource, endpoint string 1863 1864 endpoint = s.computeAPI 1865 if metadata { 1866 resource = fmt.Sprintf("/user_data/%s", key) 1867 endpoint = MetadataAPI 1868 } else { 1869 resource = fmt.Sprintf("servers/%s/user_data/%s", serverID, key) 1870 } 1871 1872 uri := fmt.Sprintf("%s/%s", strings.TrimRight(endpoint, "/"), resource) 1873 payload := new(bytes.Buffer) 1874 payload.Write(value) 1875 1876 req, err := http.NewRequest("PATCH", uri, payload) 1877 if err != nil { 1878 return err 1879 } 1880 1881 req.Header.Set("X-Auth-Token", s.Token) 1882 req.Header.Set("Content-Type", "text/plain") 1883 req.Header.Set("User-Agent", s.userAgent) 1884 1885 s.LogHTTP(req) 1886 1887 resp, err := s.client.Do(req) 1888 if resp != nil { 1889 defer resp.Body.Close() 1890 } 1891 if err != nil { 1892 return err 1893 } 1894 1895 if resp.StatusCode == http.StatusNoContent { 1896 return nil 1897 } 1898 1899 return fmt.Errorf("cannot set user_data (%d)", resp.StatusCode) 1900 } 1901 1902 // DeleteUserdata deletes a server user_data 1903 func (s *ScalewayAPI) DeleteUserdata(serverID, key string, metadata bool) error { 1904 var url, endpoint string 1905 1906 endpoint = s.computeAPI 1907 if metadata { 1908 url = fmt.Sprintf("/user_data/%s", key) 1909 endpoint = MetadataAPI 1910 } else { 1911 url = fmt.Sprintf("servers/%s/user_data/%s", serverID, key) 1912 } 1913 1914 resp, err := s.DeleteResponse(endpoint, url) 1915 if resp != nil { 1916 defer resp.Body.Close() 1917 } 1918 if err != nil { 1919 return err 1920 } 1921 1922 _, err = s.handleHTTPError([]int{http.StatusNoContent}, resp) 1923 return err 1924 } 1925 1926 // GetTasks get the list of tasks from the ScalewayAPI 1927 func (s *ScalewayAPI) GetTasks() (*[]ScalewayTask, error) { 1928 query := url.Values{} 1929 resp, err := s.GetResponsePaginate(s.computeAPI, "tasks", query) 1930 if resp != nil { 1931 defer resp.Body.Close() 1932 } 1933 if err != nil { 1934 return nil, err 1935 } 1936 1937 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 1938 if err != nil { 1939 return nil, err 1940 } 1941 var tasks ScalewayTasks 1942 1943 if err = json.Unmarshal(body, &tasks); err != nil { 1944 return nil, err 1945 } 1946 return &tasks.Tasks, nil 1947 } 1948 1949 // CheckCredentials performs a dummy check to ensure we can contact the API 1950 func (s *ScalewayAPI) CheckCredentials() error { 1951 query := url.Values{} 1952 1953 resp, err := s.GetResponsePaginate(AccountAPI, "tokens", query) 1954 if resp != nil { 1955 defer resp.Body.Close() 1956 } 1957 if err != nil { 1958 return err 1959 } 1960 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 1961 if err != nil { 1962 return err 1963 } 1964 found := false 1965 var tokens ScalewayGetTokens 1966 1967 if err = json.Unmarshal(body, &tokens); err != nil { 1968 return err 1969 } 1970 for _, token := range tokens.Tokens { 1971 if token.ID == s.Token { 1972 found = true 1973 break 1974 } 1975 } 1976 if !found { 1977 return fmt.Errorf("Invalid token %v", s.Token) 1978 } 1979 return nil 1980 } 1981 1982 // GetUserID returns the userID 1983 func (s *ScalewayAPI) GetUserID() (string, error) { 1984 resp, err := s.GetResponsePaginate(AccountAPI, fmt.Sprintf("tokens/%s", s.Token), url.Values{}) 1985 if resp != nil { 1986 defer resp.Body.Close() 1987 } 1988 if err != nil { 1989 return "", err 1990 } 1991 1992 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 1993 if err != nil { 1994 return "", err 1995 } 1996 var token ScalewayTokensDefinition 1997 1998 if err = json.Unmarshal(body, &token); err != nil { 1999 return "", err 2000 } 2001 return token.Token.UserID, nil 2002 } 2003 2004 // GetOrganization returns Organization 2005 func (s *ScalewayAPI) GetOrganization() (*ScalewayOrganizationsDefinition, error) { 2006 resp, err := s.GetResponsePaginate(AccountAPI, "organizations", url.Values{}) 2007 if resp != nil { 2008 defer resp.Body.Close() 2009 } 2010 if err != nil { 2011 return nil, err 2012 } 2013 2014 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 2015 if err != nil { 2016 return nil, err 2017 } 2018 var data ScalewayOrganizationsDefinition 2019 2020 if err = json.Unmarshal(body, &data); err != nil { 2021 return nil, err 2022 } 2023 return &data, nil 2024 } 2025 2026 // GetUser returns the user 2027 func (s *ScalewayAPI) GetUser() (*ScalewayUserDefinition, error) { 2028 userID, err := s.GetUserID() 2029 if err != nil { 2030 return nil, err 2031 } 2032 resp, err := s.GetResponsePaginate(AccountAPI, fmt.Sprintf("users/%s", userID), url.Values{}) 2033 if resp != nil { 2034 defer resp.Body.Close() 2035 } 2036 if err != nil { 2037 return nil, err 2038 } 2039 2040 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 2041 if err != nil { 2042 return nil, err 2043 } 2044 var user ScalewayUsersDefinition 2045 2046 if err = json.Unmarshal(body, &user); err != nil { 2047 return nil, err 2048 } 2049 return &user.User, nil 2050 } 2051 2052 // GetPermissions returns the permissions 2053 func (s *ScalewayAPI) GetPermissions() (*ScalewayPermissionDefinition, error) { 2054 resp, err := s.GetResponsePaginate(AccountAPI, fmt.Sprintf("tokens/%s/permissions", s.Token), url.Values{}) 2055 if resp != nil { 2056 defer resp.Body.Close() 2057 } 2058 if err != nil { 2059 return nil, err 2060 } 2061 2062 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 2063 if err != nil { 2064 return nil, err 2065 } 2066 var permissions ScalewayPermissionDefinition 2067 2068 if err = json.Unmarshal(body, &permissions); err != nil { 2069 return nil, err 2070 } 2071 return &permissions, nil 2072 } 2073 2074 // GetDashboard returns the dashboard 2075 func (s *ScalewayAPI) GetDashboard() (*ScalewayDashboard, error) { 2076 resp, err := s.GetResponsePaginate(s.computeAPI, "dashboard", url.Values{}) 2077 if resp != nil { 2078 defer resp.Body.Close() 2079 } 2080 if err != nil { 2081 return nil, err 2082 } 2083 2084 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 2085 if err != nil { 2086 return nil, err 2087 } 2088 var dashboard ScalewayDashboardResp 2089 2090 if err = json.Unmarshal(body, &dashboard); err != nil { 2091 return nil, err 2092 } 2093 return &dashboard.Dashboard, nil 2094 } 2095 2096 // GetServerID returns exactly one server matching 2097 func (s *ScalewayAPI) GetServerID(needle string) (string, error) { 2098 // Parses optional type prefix, i.e: "server:name" -> "name" 2099 _, needle = parseNeedle(needle) 2100 2101 servers, err := s.ResolveServer(needle) 2102 if err != nil { 2103 return "", fmt.Errorf("Unable to resolve server %s: %s", needle, err) 2104 } 2105 if len(servers) == 1 { 2106 return servers[0].Identifier, nil 2107 } 2108 if len(servers) == 0 { 2109 return "", fmt.Errorf("No such server: %s", needle) 2110 } 2111 return "", showResolverResults(needle, servers) 2112 } 2113 2114 func showResolverResults(needle string, results ScalewayResolverResults) error { 2115 w := tabwriter.NewWriter(os.Stderr, 20, 1, 3, ' ', 0) 2116 defer w.Flush() 2117 sort.Sort(results) 2118 fmt.Fprintf(w, " IMAGEID\tFROM\tNAME\tZONE\tARCH\n") 2119 for _, result := range results { 2120 if result.Arch == "" { 2121 result.Arch = "n/a" 2122 } 2123 fmt.Fprintf(w, "- %s\t%s\t%s\t%s\t%s\n", result.TruncIdentifier(), result.CodeName(), result.Name, result.Region, result.Arch) 2124 } 2125 return fmt.Errorf("Too many candidates for %s (%d)", needle, len(results)) 2126 } 2127 2128 // GetVolumeID returns exactly one volume matching 2129 func (s *ScalewayAPI) GetVolumeID(needle string) (string, error) { 2130 // Parses optional type prefix, i.e: "volume:name" -> "name" 2131 _, needle = parseNeedle(needle) 2132 2133 volumes, err := s.ResolveVolume(needle) 2134 if err != nil { 2135 return "", fmt.Errorf("Unable to resolve volume %s: %s", needle, err) 2136 } 2137 if len(volumes) == 1 { 2138 return volumes[0].Identifier, nil 2139 } 2140 if len(volumes) == 0 { 2141 return "", fmt.Errorf("No such volume: %s", needle) 2142 } 2143 return "", showResolverResults(needle, volumes) 2144 } 2145 2146 // GetSnapshotID returns exactly one snapshot matching 2147 func (s *ScalewayAPI) GetSnapshotID(needle string) (string, error) { 2148 // Parses optional type prefix, i.e: "snapshot:name" -> "name" 2149 _, needle = parseNeedle(needle) 2150 2151 snapshots, err := s.ResolveSnapshot(needle) 2152 if err != nil { 2153 return "", fmt.Errorf("Unable to resolve snapshot %s: %s", needle, err) 2154 } 2155 if len(snapshots) == 1 { 2156 return snapshots[0].Identifier, nil 2157 } 2158 if len(snapshots) == 0 { 2159 return "", fmt.Errorf("No such snapshot: %s", needle) 2160 } 2161 return "", showResolverResults(needle, snapshots) 2162 } 2163 2164 // FilterImagesByArch removes entry that doesn't match with architecture 2165 func FilterImagesByArch(res ScalewayResolverResults, arch string) (ret ScalewayResolverResults) { 2166 if arch == "*" { 2167 return res 2168 } 2169 for _, result := range res { 2170 if result.Arch == arch { 2171 ret = append(ret, result) 2172 } 2173 } 2174 return 2175 } 2176 2177 // FilterImagesByRegion removes entry that doesn't match with region 2178 func FilterImagesByRegion(res ScalewayResolverResults, region string) (ret ScalewayResolverResults) { 2179 if region == "*" { 2180 return res 2181 } 2182 for _, result := range res { 2183 if result.Region == region { 2184 ret = append(ret, result) 2185 } 2186 } 2187 return 2188 } 2189 2190 // GetImageID returns exactly one image matching 2191 func (s *ScalewayAPI) GetImageID(needle, arch string) (*ScalewayImageIdentifier, error) { 2192 // Parses optional type prefix, i.e: "image:name" -> "name" 2193 _, needle = parseNeedle(needle) 2194 2195 images, err := s.ResolveImage(needle) 2196 if err != nil { 2197 return nil, fmt.Errorf("Unable to resolve image %s: %s", needle, err) 2198 } 2199 images = FilterImagesByArch(images, arch) 2200 images = FilterImagesByRegion(images, s.Region) 2201 if len(images) == 1 { 2202 return &ScalewayImageIdentifier{ 2203 Identifier: images[0].Identifier, 2204 Arch: images[0].Arch, 2205 // FIXME region, owner hardcoded 2206 Region: images[0].Region, 2207 Owner: "", 2208 }, nil 2209 } 2210 if len(images) == 0 { 2211 return nil, fmt.Errorf("No such image (zone %s, arch %s) : %s", s.Region, arch, needle) 2212 } 2213 return nil, showResolverResults(needle, images) 2214 } 2215 2216 // GetSecurityGroups returns a ScalewaySecurityGroups 2217 func (s *ScalewayAPI) GetSecurityGroups() (*ScalewayGetSecurityGroups, error) { 2218 resp, err := s.GetResponsePaginate(s.computeAPI, "security_groups", url.Values{}) 2219 if resp != nil { 2220 defer resp.Body.Close() 2221 } 2222 if err != nil { 2223 return nil, err 2224 } 2225 2226 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 2227 if err != nil { 2228 return nil, err 2229 } 2230 var securityGroups ScalewayGetSecurityGroups 2231 2232 if err = json.Unmarshal(body, &securityGroups); err != nil { 2233 return nil, err 2234 } 2235 return &securityGroups, nil 2236 } 2237 2238 // GetSecurityGroupRules returns a ScalewaySecurityGroupRules 2239 func (s *ScalewayAPI) GetSecurityGroupRules(groupID string) (*ScalewayGetSecurityGroupRules, error) { 2240 resp, err := s.GetResponsePaginate(s.computeAPI, fmt.Sprintf("security_groups/%s/rules", groupID), url.Values{}) 2241 if resp != nil { 2242 defer resp.Body.Close() 2243 } 2244 if err != nil { 2245 return nil, err 2246 } 2247 2248 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 2249 if err != nil { 2250 return nil, err 2251 } 2252 var securityGroupRules ScalewayGetSecurityGroupRules 2253 2254 if err = json.Unmarshal(body, &securityGroupRules); err != nil { 2255 return nil, err 2256 } 2257 return &securityGroupRules, nil 2258 } 2259 2260 // GetASecurityGroupRule returns a ScalewaySecurityGroupRule 2261 func (s *ScalewayAPI) GetASecurityGroupRule(groupID string, rulesID string) (*ScalewayGetSecurityGroupRule, error) { 2262 resp, err := s.GetResponsePaginate(s.computeAPI, fmt.Sprintf("security_groups/%s/rules/%s", groupID, rulesID), url.Values{}) 2263 if resp != nil { 2264 defer resp.Body.Close() 2265 } 2266 if err != nil { 2267 return nil, err 2268 } 2269 2270 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 2271 if err != nil { 2272 return nil, err 2273 } 2274 var securityGroupRules ScalewayGetSecurityGroupRule 2275 2276 if err = json.Unmarshal(body, &securityGroupRules); err != nil { 2277 return nil, err 2278 } 2279 return &securityGroupRules, nil 2280 } 2281 2282 // GetASecurityGroup returns a ScalewaySecurityGroup 2283 func (s *ScalewayAPI) GetASecurityGroup(groupsID string) (*ScalewayGetSecurityGroup, error) { 2284 resp, err := s.GetResponsePaginate(s.computeAPI, fmt.Sprintf("security_groups/%s", groupsID), url.Values{}) 2285 if resp != nil { 2286 defer resp.Body.Close() 2287 } 2288 if err != nil { 2289 return nil, err 2290 } 2291 2292 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 2293 if err != nil { 2294 return nil, err 2295 } 2296 var securityGroups ScalewayGetSecurityGroup 2297 2298 if err = json.Unmarshal(body, &securityGroups); err != nil { 2299 return nil, err 2300 } 2301 return &securityGroups, nil 2302 } 2303 2304 // PostSecurityGroup posts a group on a server 2305 func (s *ScalewayAPI) PostSecurityGroup(group ScalewayNewSecurityGroup) error { 2306 resp, err := s.PostResponse(s.computeAPI, "security_groups", group) 2307 if resp != nil { 2308 defer resp.Body.Close() 2309 } 2310 if err != nil { 2311 return err 2312 } 2313 2314 _, err = s.handleHTTPError([]int{http.StatusCreated}, resp) 2315 return err 2316 } 2317 2318 // PostSecurityGroupRule posts a rule on a server 2319 func (s *ScalewayAPI) PostSecurityGroupRule(SecurityGroupID string, rules ScalewayNewSecurityGroupRule) error { 2320 resp, err := s.PostResponse(s.computeAPI, fmt.Sprintf("security_groups/%s/rules", SecurityGroupID), rules) 2321 if resp != nil { 2322 defer resp.Body.Close() 2323 } 2324 if err != nil { 2325 return err 2326 } 2327 2328 _, err = s.handleHTTPError([]int{http.StatusCreated}, resp) 2329 return err 2330 } 2331 2332 // DeleteSecurityGroup deletes a SecurityGroup 2333 func (s *ScalewayAPI) DeleteSecurityGroup(securityGroupID string) error { 2334 resp, err := s.DeleteResponse(s.computeAPI, fmt.Sprintf("security_groups/%s", securityGroupID)) 2335 if resp != nil { 2336 defer resp.Body.Close() 2337 } 2338 if err != nil { 2339 return err 2340 } 2341 2342 _, err = s.handleHTTPError([]int{http.StatusNoContent}, resp) 2343 return err 2344 } 2345 2346 // PutSecurityGroup updates a SecurityGroup 2347 func (s *ScalewayAPI) PutSecurityGroup(group ScalewayUpdateSecurityGroup, securityGroupID string) error { 2348 resp, err := s.PutResponse(s.computeAPI, fmt.Sprintf("security_groups/%s", securityGroupID), group) 2349 if resp != nil { 2350 defer resp.Body.Close() 2351 } 2352 if err != nil { 2353 return err 2354 } 2355 2356 _, err = s.handleHTTPError([]int{http.StatusOK}, resp) 2357 return err 2358 } 2359 2360 // PutSecurityGroupRule updates a SecurityGroupRule 2361 func (s *ScalewayAPI) PutSecurityGroupRule(rules ScalewayNewSecurityGroupRule, securityGroupID, RuleID string) error { 2362 resp, err := s.PutResponse(s.computeAPI, fmt.Sprintf("security_groups/%s/rules/%s", securityGroupID, RuleID), rules) 2363 if resp != nil { 2364 defer resp.Body.Close() 2365 } 2366 if err != nil { 2367 return err 2368 } 2369 2370 _, err = s.handleHTTPError([]int{http.StatusOK}, resp) 2371 return err 2372 } 2373 2374 // DeleteSecurityGroupRule deletes a SecurityGroupRule 2375 func (s *ScalewayAPI) DeleteSecurityGroupRule(SecurityGroupID, RuleID string) error { 2376 resp, err := s.DeleteResponse(s.computeAPI, fmt.Sprintf("security_groups/%s/rules/%s", SecurityGroupID, RuleID)) 2377 if resp != nil { 2378 defer resp.Body.Close() 2379 } 2380 if err != nil { 2381 return err 2382 } 2383 2384 _, err = s.handleHTTPError([]int{http.StatusNoContent}, resp) 2385 return err 2386 } 2387 2388 // GetContainers returns a ScalewayGetContainers 2389 func (s *ScalewayAPI) GetContainers() (*ScalewayGetContainers, error) { 2390 resp, err := s.GetResponsePaginate(s.computeAPI, "containers", url.Values{}) 2391 if resp != nil { 2392 defer resp.Body.Close() 2393 } 2394 if err != nil { 2395 return nil, err 2396 } 2397 2398 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 2399 if err != nil { 2400 return nil, err 2401 } 2402 var containers ScalewayGetContainers 2403 2404 if err = json.Unmarshal(body, &containers); err != nil { 2405 return nil, err 2406 } 2407 return &containers, nil 2408 } 2409 2410 // GetContainerDatas returns a ScalewayGetContainerDatas 2411 func (s *ScalewayAPI) GetContainerDatas(container string) (*ScalewayGetContainerDatas, error) { 2412 resp, err := s.GetResponsePaginate(s.computeAPI, fmt.Sprintf("containers/%s", container), url.Values{}) 2413 if resp != nil { 2414 defer resp.Body.Close() 2415 } 2416 if err != nil { 2417 return nil, err 2418 } 2419 2420 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 2421 if err != nil { 2422 return nil, err 2423 } 2424 var datas ScalewayGetContainerDatas 2425 2426 if err = json.Unmarshal(body, &datas); err != nil { 2427 return nil, err 2428 } 2429 return &datas, nil 2430 } 2431 2432 // GetIPS returns a ScalewayGetIPS 2433 func (s *ScalewayAPI) GetIPS() (*ScalewayGetIPS, error) { 2434 resp, err := s.GetResponsePaginate(s.computeAPI, "ips", url.Values{}) 2435 if resp != nil { 2436 defer resp.Body.Close() 2437 } 2438 if err != nil { 2439 return nil, err 2440 } 2441 2442 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 2443 if err != nil { 2444 return nil, err 2445 } 2446 var ips ScalewayGetIPS 2447 2448 if err = json.Unmarshal(body, &ips); err != nil { 2449 return nil, err 2450 } 2451 return &ips, nil 2452 } 2453 2454 // NewIP returns a new IP 2455 func (s *ScalewayAPI) NewIP() (*ScalewayGetIP, error) { 2456 var orga struct { 2457 Organization string `json:"organization"` 2458 } 2459 orga.Organization = s.Organization 2460 resp, err := s.PostResponse(s.computeAPI, "ips", orga) 2461 if resp != nil { 2462 defer resp.Body.Close() 2463 } 2464 if err != nil { 2465 return nil, err 2466 } 2467 2468 body, err := s.handleHTTPError([]int{http.StatusCreated}, resp) 2469 if err != nil { 2470 return nil, err 2471 } 2472 var ip ScalewayGetIP 2473 2474 if err = json.Unmarshal(body, &ip); err != nil { 2475 return nil, err 2476 } 2477 return &ip, nil 2478 } 2479 2480 // AttachIP attachs an IP to a server 2481 func (s *ScalewayAPI) AttachIP(ipID, serverID string) error { 2482 var update struct { 2483 Address string `json:"address"` 2484 ID string `json:"id"` 2485 Reverse *string `json:"reverse"` 2486 Organization string `json:"organization"` 2487 Server string `json:"server"` 2488 } 2489 2490 ip, err := s.GetIP(ipID) 2491 if err != nil { 2492 return err 2493 } 2494 update.Address = ip.IP.Address 2495 update.ID = ip.IP.ID 2496 update.Organization = ip.IP.Organization 2497 update.Server = serverID 2498 resp, err := s.PutResponse(s.computeAPI, fmt.Sprintf("ips/%s", ipID), update) 2499 if err != nil { 2500 return err 2501 } 2502 _, err = s.handleHTTPError([]int{http.StatusOK}, resp) 2503 return err 2504 } 2505 2506 // DetachIP detaches an IP from a server 2507 func (s *ScalewayAPI) DetachIP(ipID string) error { 2508 ip, err := s.GetIP(ipID) 2509 if err != nil { 2510 return err 2511 } 2512 ip.IP.Server = nil 2513 resp, err := s.PutResponse(s.computeAPI, fmt.Sprintf("ips/%s", ipID), ip.IP) 2514 if resp != nil { 2515 defer resp.Body.Close() 2516 } 2517 if err != nil { 2518 return err 2519 } 2520 _, err = s.handleHTTPError([]int{http.StatusOK}, resp) 2521 return err 2522 } 2523 2524 // DeleteIP deletes an IP 2525 func (s *ScalewayAPI) DeleteIP(ipID string) error { 2526 resp, err := s.DeleteResponse(s.computeAPI, fmt.Sprintf("ips/%s", ipID)) 2527 if resp != nil { 2528 defer resp.Body.Close() 2529 } 2530 if err != nil { 2531 return err 2532 } 2533 _, err = s.handleHTTPError([]int{http.StatusNoContent}, resp) 2534 return err 2535 } 2536 2537 // GetIP returns a ScalewayGetIP 2538 func (s *ScalewayAPI) GetIP(ipID string) (*ScalewayGetIP, error) { 2539 resp, err := s.GetResponsePaginate(s.computeAPI, fmt.Sprintf("ips/%s", ipID), url.Values{}) 2540 if resp != nil { 2541 defer resp.Body.Close() 2542 } 2543 if err != nil { 2544 return nil, err 2545 } 2546 2547 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 2548 if err != nil { 2549 return nil, err 2550 } 2551 var ip ScalewayGetIP 2552 2553 if err = json.Unmarshal(body, &ip); err != nil { 2554 return nil, err 2555 } 2556 return &ip, nil 2557 } 2558 2559 // GetQuotas returns a ScalewayGetQuotas 2560 func (s *ScalewayAPI) GetQuotas() (*ScalewayGetQuotas, error) { 2561 resp, err := s.GetResponsePaginate(AccountAPI, fmt.Sprintf("organizations/%s/quotas", s.Organization), url.Values{}) 2562 if resp != nil { 2563 defer resp.Body.Close() 2564 } 2565 if err != nil { 2566 return nil, err 2567 } 2568 2569 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 2570 if err != nil { 2571 return nil, err 2572 } 2573 var quotas ScalewayGetQuotas 2574 2575 if err = json.Unmarshal(body, "as); err != nil { 2576 return nil, err 2577 } 2578 return "as, nil 2579 } 2580 2581 // GetBootscriptID returns exactly one bootscript matching 2582 func (s *ScalewayAPI) GetBootscriptID(needle, arch string) (string, error) { 2583 // Parses optional type prefix, i.e: "bootscript:name" -> "name" 2584 _, needle = parseNeedle(needle) 2585 2586 bootscripts, err := s.ResolveBootscript(needle) 2587 if err != nil { 2588 return "", fmt.Errorf("Unable to resolve bootscript %s: %s", needle, err) 2589 } 2590 bootscripts.FilterByArch(arch) 2591 if len(bootscripts) == 1 { 2592 return bootscripts[0].Identifier, nil 2593 } 2594 if len(bootscripts) == 0 { 2595 return "", fmt.Errorf("No such bootscript: %s", needle) 2596 } 2597 return "", showResolverResults(needle, bootscripts) 2598 } 2599 2600 func rootNetDial(network, addr string) (net.Conn, error) { 2601 dialer := net.Dialer{ 2602 Timeout: 10 * time.Second, 2603 KeepAlive: 10 * time.Second, 2604 } 2605 2606 // bruteforce privileged ports 2607 var localAddr net.Addr 2608 var err error 2609 for port := 1; port <= 1024; port++ { 2610 localAddr, err = net.ResolveTCPAddr("tcp", fmt.Sprintf(":%d", port)) 2611 2612 // this should never happen 2613 if err != nil { 2614 return nil, err 2615 } 2616 2617 dialer.LocalAddr = localAddr 2618 2619 conn, err := dialer.Dial(network, addr) 2620 2621 // if err is nil, dialer.Dial succeed, so let's go 2622 // else, err != nil, but we don't care 2623 if err == nil { 2624 return conn, nil 2625 } 2626 } 2627 // if here, all privileged ports were tried without success 2628 return nil, fmt.Errorf("bind: permission denied, are you root ?") 2629 } 2630 2631 // SetPassword register the password 2632 func (s *ScalewayAPI) SetPassword(password string) { 2633 s.password = password 2634 } 2635 2636 // GetMarketPlaceImages returns images from marketplace 2637 func (s *ScalewayAPI) GetMarketPlaceImages(uuidImage string) (*MarketImages, error) { 2638 resp, err := s.GetResponsePaginate(MarketplaceAPI, fmt.Sprintf("images/%s", uuidImage), url.Values{}) 2639 if resp != nil { 2640 defer resp.Body.Close() 2641 } 2642 if err != nil { 2643 return nil, err 2644 } 2645 2646 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 2647 if err != nil { 2648 return nil, err 2649 } 2650 var ret MarketImages 2651 2652 if uuidImage != "" { 2653 ret.Images = make([]MarketImage, 1) 2654 2655 var img MarketImage 2656 2657 if err = json.Unmarshal(body, &img); err != nil { 2658 return nil, err 2659 } 2660 ret.Images[0] = img 2661 } else { 2662 if err = json.Unmarshal(body, &ret); err != nil { 2663 return nil, err 2664 } 2665 } 2666 return &ret, nil 2667 } 2668 2669 // GetMarketPlaceImageVersions returns image version 2670 func (s *ScalewayAPI) GetMarketPlaceImageVersions(uuidImage, uuidVersion string) (*MarketVersions, error) { 2671 resp, err := s.GetResponsePaginate(MarketplaceAPI, fmt.Sprintf("images/%v/versions/%s", uuidImage, uuidVersion), url.Values{}) 2672 if resp != nil { 2673 defer resp.Body.Close() 2674 } 2675 if err != nil { 2676 return nil, err 2677 } 2678 2679 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 2680 if err != nil { 2681 return nil, err 2682 } 2683 var ret MarketVersions 2684 2685 if uuidImage != "" { 2686 var version MarketVersion 2687 ret.Versions = make([]MarketVersionDefinition, 1) 2688 2689 if err = json.Unmarshal(body, &version); err != nil { 2690 return nil, err 2691 } 2692 ret.Versions[0] = version.Version 2693 } else { 2694 if err = json.Unmarshal(body, &ret); err != nil { 2695 return nil, err 2696 } 2697 } 2698 return &ret, nil 2699 } 2700 2701 // GetMarketPlaceImageCurrentVersion return the image current version 2702 func (s *ScalewayAPI) GetMarketPlaceImageCurrentVersion(uuidImage string) (*MarketVersion, error) { 2703 resp, err := s.GetResponsePaginate(MarketplaceAPI, fmt.Sprintf("images/%v/versions/current", uuidImage), url.Values{}) 2704 if resp != nil { 2705 defer resp.Body.Close() 2706 } 2707 if err != nil { 2708 return nil, err 2709 } 2710 2711 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 2712 if err != nil { 2713 return nil, err 2714 } 2715 var ret MarketVersion 2716 2717 if err = json.Unmarshal(body, &ret); err != nil { 2718 return nil, err 2719 } 2720 return &ret, nil 2721 } 2722 2723 // GetMarketPlaceLocalImages returns images from local region 2724 func (s *ScalewayAPI) GetMarketPlaceLocalImages(uuidImage, uuidVersion, uuidLocalImage string) (*MarketLocalImages, error) { 2725 resp, err := s.GetResponsePaginate(MarketplaceAPI, fmt.Sprintf("images/%v/versions/%s/local_images/%s", uuidImage, uuidVersion, uuidLocalImage), url.Values{}) 2726 if resp != nil { 2727 defer resp.Body.Close() 2728 } 2729 if err != nil { 2730 return nil, err 2731 } 2732 body, err := s.handleHTTPError([]int{http.StatusOK}, resp) 2733 if err != nil { 2734 return nil, err 2735 } 2736 var ret MarketLocalImages 2737 if uuidLocalImage != "" { 2738 var localImage MarketLocalImage 2739 ret.LocalImages = make([]MarketLocalImageDefinition, 1) 2740 2741 if err = json.Unmarshal(body, &localImage); err != nil { 2742 return nil, err 2743 } 2744 ret.LocalImages[0] = localImage.LocalImages 2745 } else { 2746 if err = json.Unmarshal(body, &ret); err != nil { 2747 return nil, err 2748 } 2749 } 2750 return &ret, nil 2751 } 2752 2753 // PostMarketPlaceImage adds new image 2754 func (s *ScalewayAPI) PostMarketPlaceImage(images MarketImage) error { 2755 resp, err := s.PostResponse(MarketplaceAPI, "images/", images) 2756 if resp != nil { 2757 defer resp.Body.Close() 2758 } 2759 if err != nil { 2760 return err 2761 } 2762 _, err = s.handleHTTPError([]int{http.StatusAccepted}, resp) 2763 return err 2764 } 2765 2766 // PostMarketPlaceImageVersion adds new image version 2767 func (s *ScalewayAPI) PostMarketPlaceImageVersion(uuidImage string, version MarketVersion) error { 2768 resp, err := s.PostResponse(MarketplaceAPI, fmt.Sprintf("images/%v/versions", uuidImage), version) 2769 if resp != nil { 2770 defer resp.Body.Close() 2771 } 2772 if err != nil { 2773 return err 2774 } 2775 _, err = s.handleHTTPError([]int{http.StatusAccepted}, resp) 2776 return err 2777 } 2778 2779 // PostMarketPlaceLocalImage adds new local image 2780 func (s *ScalewayAPI) PostMarketPlaceLocalImage(uuidImage, uuidVersion, uuidLocalImage string, local MarketLocalImage) error { 2781 resp, err := s.PostResponse(MarketplaceAPI, fmt.Sprintf("images/%v/versions/%s/local_images/%v", uuidImage, uuidVersion, uuidLocalImage), local) 2782 if resp != nil { 2783 defer resp.Body.Close() 2784 } 2785 if err != nil { 2786 return err 2787 } 2788 _, err = s.handleHTTPError([]int{http.StatusAccepted}, resp) 2789 return err 2790 } 2791 2792 // PutMarketPlaceImage updates image 2793 func (s *ScalewayAPI) PutMarketPlaceImage(uudiImage string, images MarketImage) error { 2794 resp, err := s.PutResponse(MarketplaceAPI, fmt.Sprintf("images/%v", uudiImage), images) 2795 if resp != nil { 2796 defer resp.Body.Close() 2797 } 2798 if err != nil { 2799 return err 2800 } 2801 _, err = s.handleHTTPError([]int{http.StatusOK}, resp) 2802 return err 2803 } 2804 2805 // PutMarketPlaceImageVersion updates image version 2806 func (s *ScalewayAPI) PutMarketPlaceImageVersion(uuidImage, uuidVersion string, version MarketVersion) error { 2807 resp, err := s.PutResponse(MarketplaceAPI, fmt.Sprintf("images/%v/versions/%v", uuidImage, uuidVersion), version) 2808 if resp != nil { 2809 defer resp.Body.Close() 2810 } 2811 if err != nil { 2812 return err 2813 } 2814 _, err = s.handleHTTPError([]int{http.StatusOK}, resp) 2815 return err 2816 } 2817 2818 // PutMarketPlaceLocalImage updates local image 2819 func (s *ScalewayAPI) PutMarketPlaceLocalImage(uuidImage, uuidVersion, uuidLocalImage string, local MarketLocalImage) error { 2820 resp, err := s.PostResponse(MarketplaceAPI, fmt.Sprintf("images/%v/versions/%s/local_images/%v", uuidImage, uuidVersion, uuidLocalImage), local) 2821 if resp != nil { 2822 defer resp.Body.Close() 2823 } 2824 if err != nil { 2825 return err 2826 } 2827 _, err = s.handleHTTPError([]int{http.StatusOK}, resp) 2828 return err 2829 } 2830 2831 // DeleteMarketPlaceImage deletes image 2832 func (s *ScalewayAPI) DeleteMarketPlaceImage(uudImage string) error { 2833 resp, err := s.DeleteResponse(MarketplaceAPI, fmt.Sprintf("images/%v", uudImage)) 2834 if resp != nil { 2835 defer resp.Body.Close() 2836 } 2837 if err != nil { 2838 return err 2839 } 2840 _, err = s.handleHTTPError([]int{http.StatusNoContent}, resp) 2841 return err 2842 } 2843 2844 // DeleteMarketPlaceImageVersion delete image version 2845 func (s *ScalewayAPI) DeleteMarketPlaceImageVersion(uuidImage, uuidVersion string) error { 2846 resp, err := s.DeleteResponse(MarketplaceAPI, fmt.Sprintf("images/%v/versions/%v", uuidImage, uuidVersion)) 2847 if resp != nil { 2848 defer resp.Body.Close() 2849 } 2850 if err != nil { 2851 return err 2852 } 2853 _, err = s.handleHTTPError([]int{http.StatusNoContent}, resp) 2854 return err 2855 } 2856 2857 // DeleteMarketPlaceLocalImage deletes local image 2858 func (s *ScalewayAPI) DeleteMarketPlaceLocalImage(uuidImage, uuidVersion, uuidLocalImage string) error { 2859 resp, err := s.DeleteResponse(MarketplaceAPI, fmt.Sprintf("images/%v/versions/%s/local_images/%v", uuidImage, uuidVersion, uuidLocalImage)) 2860 if resp != nil { 2861 defer resp.Body.Close() 2862 } 2863 if err != nil { 2864 return err 2865 } 2866 _, err = s.handleHTTPError([]int{http.StatusNoContent}, resp) 2867 return err 2868 } 2869 2870 // ResolveTTYUrl return an URL to get a tty 2871 func (s *ScalewayAPI) ResolveTTYUrl() string { 2872 switch s.Region { 2873 case "par1", "": 2874 return "https://tty-par1.scaleway.com/v2/" 2875 case "ams1": 2876 return "https://tty-ams1.scaleway.com" 2877 } 2878 return "" 2879 }