1 /+ 2 3 Digital Ocean API 4 2014 - 2020 by Laeeth Isharc, Kaleidic Associates Advisory Limited, and Symmetry Investments 5 6 --- 7 import symmetry.api.digitalocean; 8 import std.json; 9 import std.stdio; 10 11 12 void main(string[] args) 13 { 14 auto ocean=OceanAPI(OceanAPIKey); 15 auto result=Droplet.create( ocean, 16 "newemail.symmetry.ssociates.com", 17 OceanRegion.lon1, 18 "1Gb", 19 OceanImageId("debian-8-x64"), 20 ["ab:21:7e:22:e5:4c:95:23:e9:aa:f8:59:be:5f:96:24"]); 21 writefln(result.prettyPrint); 22 auto actions=ocean.listDroplets; 23 writefln(actions.prettyPrint); 24 /* auto droplet=ocean.findDroplet("hoelderlin.symmetry.ssociates.com").result.retrieve; 25 writefln(droplet.prettyPrint); 26 auto keys=ocean.listKeys; 27 writefln("%s",keys); */ 28 } 29 30 --- 31 32 really not tested 33 so far: reasonable results for 34 listDomains 35 listDroplets 36 listSizes 37 listKeys 38 listImages 39 findDroplet 40 Droplet.retrieve 41 +/ 42 43 /// 44 module symmetry.api.digitalocean; 45 import std.stdio; 46 import std.json; 47 import std.net.curl; 48 import std.exception : enforce,assumeUnique; 49 import std.conv:to; 50 import std.algorithm:countUntil,map,each; 51 import std.traits:EnumMembers; 52 import std.array:array,appender; 53 import std.format:format; 54 import std.variant:Algebraic; 55 56 /** 57 Implemented in the D Programming Language 2015 by Laeeth Isharc and Kaleidic Associates 58 Boost Licensed 59 Use at your own risk - this is not tested at all and if you end up deleting all your 60 instances and creating 10,000 pricey new ones then it will not be my fault 61 */ 62 63 /// 64 shared static this() 65 { 66 OceanRegions=[EnumMembers!OceanRegion].map!(a=>a.toString).array.assumeUnique; 67 DropletActions=[EnumMembers!DropletAction].map!(a=>a.toString).array.assumeUnique; 68 OceanImages= 69 [ 70 OceanGlobalImage(false,"CoreOS-766.4.0-(beta)",13578467,OceanDistro.CoreOS,""), 71 OceanGlobalImage(false,"CentOS-5.10-x64",6372321,OceanDistro.CentOS,""), 72 OceanGlobalImage(false,"5.10 x32",6372425, OceanDistro.CentOS), 73 OceanGlobalImage(false,"6.0 x64",6372581, OceanDistro.Debian), 74 OceanGlobalImage(false,"6.0 x32",6372662, OceanDistro.Debian), 75 OceanGlobalImage(false,"21 x64",9640922, OceanDistro.Fedora), 76 OceanGlobalImage(false,"10.1",10144573, OceanDistro.FreeBSD), 77 OceanGlobalImage(false,"12.04.5 x64",10321756, OceanDistro.Ubuntu), 78 OceanGlobalImage(false,"12.04.5 x32",10321777, OceanDistro.Ubuntu), 79 OceanGlobalImage(false,"7.0 x64",10322059, OceanDistro.Debian), 80 OceanGlobalImage(false,"7.0 x32",10322378, OceanDistro.Debian), 81 OceanGlobalImage(false,"7 x64",10322623, OceanDistro.CentOS), 82 OceanGlobalImage(false,"22 x64",12065782, OceanDistro.Fedora), 83 OceanGlobalImage(false,"15.04 x64",12658446, OceanDistro.Ubuntu), 84 OceanGlobalImage(false,"15.04 x32",12660649, OceanDistro.Ubuntu), 85 OceanGlobalImage(false,"8.1 x64",12778278, OceanDistro.Debian), 86 OceanGlobalImage(false,"8.1 x32",12778337, OceanDistro.Debian), 87 OceanGlobalImage(false,"14.04 x64",13089493, OceanDistro.Ubuntu), 88 OceanGlobalImage(false,"14.04 x32",13089823, OceanDistro.Ubuntu), 89 OceanGlobalImage(false,"6.7 x64",13090046, OceanDistro.CentOS), 90 OceanGlobalImage(false,"6.7 x32",13090097, OceanDistro.CentOS), 91 OceanGlobalImage(false,"10.2",13321858, OceanDistro.FreeBSD), 92 OceanGlobalImage(false,"815.0.0 (alpha)",13683512, OceanDistro.CoreOS), 93 OceanGlobalImage(false,"766.4.0 (stable)",13750582, OceanDistro.CoreOS), 94 OceanGlobalImage(false,"FreeBSD AMP on 10.1",10163059, OceanDistro.FreeBSD), 95 OceanGlobalImage(true,"Drone on 14.04",11774848, OceanDistro.Ubuntu,"Drone"), 96 OceanGlobalImage(true,"Cassandra on 14.04",12540744, OceanDistro.Ubuntu,"Cassandra"), 97 OceanGlobalImage(true,"ELK Logging Stack on 14.04",12542038, OceanDistro.Ubuntu,"ELK Logging"), 98 OceanGlobalImage(true,"Django on 14.04",12740667, OceanDistro.Ubuntu,"Django"), 99 OceanGlobalImage(true,"Mumble Server (murmur) on 14.04",12914152, OceanDistro.Ubuntu,"murmur"), 100 OceanGlobalImage(true,"Joomla! 3.4.3 on 14.04",13014869, OceanDistro.Ubuntu,"Joomla"), 101 OceanGlobalImage(true,"Magento-1.9.2.1 CE on 14.04",13115155, OceanDistro.Ubuntu,"Magento"), 102 OceanGlobalImage(true,"MongoDB 3.0.5 on 14.04",13115659, OceanDistro.Ubuntu,"MongoDB"), 103 OceanGlobalImage(true,"LEMP on 14.04",13138234, OceanDistro.Ubuntu,"LEMP"), 104 OceanGlobalImage(true,"LAMP on 14.04",13138235, OceanDistro.Ubuntu,"LAMP"), 105 OceanGlobalImage(true,"MediaWiki on 14.04",13185409, OceanDistro.Ubuntu,"MediaWiki"), 106 OceanGlobalImage(true,"WordPress on 14.04",13229890, OceanDistro.Ubuntu,"WordPress"), 107 OceanGlobalImage(true,"Ruby on Rails on 14.04 (Postgres, Nginx, Unicorn)",13400199, OceanDistro.Ubuntu,"Ruby on Rails"), 108 OceanGlobalImage(true,"MEAN on 14.04",13413549, OceanDistro.Ubuntu,"MEAN"), 109 OceanGlobalImage(true,"Drupal 7.39 on 14.04",13414327, OceanDistro.Ubuntu,"Drupal"), 110 OceanGlobalImage(true,"Docker 1.8.2 on 14.04",13495049, OceanDistro.Ubuntu,"Docker"), 111 OceanGlobalImage(true,"node v4.1.0 on 14.04",13586846, OceanDistro.Ubuntu,"nodejs"), 112 OceanGlobalImage(true,"Redis 3.0.4 on 14.04",13601457, OceanDistro.Ubuntu,"Redis"), 113 OceanGlobalImage(true,"ownCloud 8.1.3 on 14.04",13603669, OceanDistro.Ubuntu,"ownCloud"), 114 OceanGlobalImage(true,"GitLab 8.0.1 CE on 14.04",13670476, OceanDistro.Ubuntu,"GitLab"), 115 OceanGlobalImage(true,"Ghost 0.7.1 on 14.04",13750431, OceanDistro.Ubuntu,"Ghost"), 116 OceanGlobalImage(true,"Dokku v0.4.1 on 14.04",13750783, OceanDistro.Ubuntu,"Dokku"), 117 OceanGlobalImage(true,"Discourse on 14.04",13846109, OceanDistro.Ubuntu,"Discourse"), 118 OceanGlobalImage(true,"PHPMyAdmin on 14.04",11730661, OceanDistro.Ubuntu,"PHPMyAdmin"), 119 OceanGlobalImage(true,"Redmine on 14.04",12438838, OceanDistro.Ubuntu,"Redmine"), 120 ]; 121 } 122 123 /// 124 string joinUrl(string url, string endpoint) 125 { 126 enforce(url.length>0, "broken url"); 127 if (url[$-1]=='/') 128 url=url[0..$-1]; 129 return url~"/"~endpoint; 130 } 131 132 133 /// 134 struct OceanAPI 135 { 136 string endpoint = "https://api.digitalocean.com/v2/"; 137 string token; 138 139 this(string token) 140 { 141 this.token=token; 142 } 143 this(string endpoint, string token) 144 { 145 this.endpoint=endpoint; 146 this.token=token; 147 } 148 } 149 150 /// 151 JSONValue request(OceanAPI api, string url, HTTP.Method method=HTTP.Method.get, JSONValue params=JSONValue(null)) 152 { 153 enforce(api.token.length>0,"no token provided"); 154 url=api.endpoint.joinUrl(url); 155 auto client=HTTP(url); 156 client.addRequestHeader("Authorization", "Bearer "~api.token); 157 auto response=appender!(ubyte[]); 158 client.method=method; 159 switch(method) with(HTTP.Method) 160 { 161 case del: 162 client.setPostData(cast(void[])params.toString,"application/x-www-form-urlencoded"); 163 break; 164 case get,head: 165 client.setPostData(cast(void[])params.toString,"application/json"); 166 break; 167 default: 168 client.setPostData(cast(void[])params.toString,"application/json"); 169 break; 170 } 171 client.onReceive = (ubyte[] data) 172 { 173 response.put(data); 174 return data.length; 175 }; 176 client.perform(); // rely on curl to throw exceptions on 204, >=500 177 return parseJSON(cast(string)response.data); 178 } 179 180 181 /// List all Actions 182 auto listActions(OceanAPI api) 183 { 184 return api.request("actions",HTTP.Method.get); 185 } 186 187 /// retrieve existing Action 188 auto retrieveAction(OceanAPI api, string id) 189 { 190 return api.request("actions/"~id, HTTP.Method.get); 191 } 192 193 /// 194 auto allNeighbours(OceanAPI api) 195 { 196 return api.request("reports/droplet_neighbors",HTTP.Method.get); 197 } 198 199 /// 200 auto listUpgrades(OceanAPI api) 201 { 202 return api.request("droplet_upgrades",HTTP.Method.get); 203 } 204 205 /// List all Domains (managed through Ocean DNS interface) 206 auto listDomains(OceanAPI api) 207 { 208 return api.request("domains", HTTP.Method.get); 209 } 210 211 /// 212 struct OceanDomain 213 { 214 OceanAPI api; 215 string id; 216 alias id this; 217 this(OceanAPI api, string id) 218 { 219 this.api=api; 220 this.id=id; 221 } 222 // Create new Domain 223 static auto create(OceanAPI api, string name, string ip) 224 { 225 JSONValue params; 226 params["name"]=name; 227 params["ip_address"]=ip; 228 return api.request("domains", HTTP.Method.post, params); 229 } 230 auto request(string url, HTTP.Method method=HTTP.Method.get, JSONValue params=JSONValue(null)) 231 { 232 return api.request(url,method,params); 233 } 234 } 235 236 /// Retrieve an existing Domain 237 auto get(OceanDomain domain) 238 { 239 return domain.request("domains/"~domain.id, HTTP.Method.get); 240 } 241 242 /// Delete a Domain 243 auto del(OceanDomain domain) 244 { 245 return domain.request("domains/"~domain.id, HTTP.Method.del); 246 } 247 248 /// List all Domain Records 249 auto listDomainRecords(OceanDomain domain) 250 { 251 return domain.request(format("domains/%s/records",domain.id), HTTP.Method.get); 252 } 253 254 /// Create a new Domain Record 255 auto createRecord(OceanDomain domain, string rtype=null, string name=null, string data=null, 256 string priority=null, string port=null, string weight=null) 257 { 258 JSONValue params; 259 params["type"]=rtype; 260 if(name.length>0) 261 params["name"]=name; 262 if(data.length>0) 263 params["data"]=data; 264 if(priority.length>0) 265 params["priority"]=priority; 266 if(port.length>0) 267 params["port"]=port; 268 if(weight.length>0) 269 params["weight"]=weight; 270 return domain.request( 271 format("domains/%s/records",domain.id), HTTP.Method.post, params); 272 } 273 274 /// Retrieve an existing Domain Record 275 auto getRecord(OceanDomain domain, string recordId) 276 { 277 return domain.request( 278 format("domains/%s/records/%s",domain.id,recordId), HTTP.Method.get); 279 } 280 281 /// Delete a Domain Record 282 auto delRecord(OceanDomain domain, string recordId) 283 { 284 return domain.request( 285 format("domains/%s/records/%s",domain.id,recordId), HTTP.Method.del); 286 } 287 288 /// Update a Domain Record 289 auto updateRecord(OceanDomain domain, string recordId,string name) 290 { 291 JSONValue params; 292 params["name"] = name; 293 return domain.request(format("domains/%s/records/%s",domain.id, recordId), HTTP.Method.put, params); 294 } 295 296 297 /// list all droplets 298 auto listDroplets(OceanAPI api) 299 { 300 return api.request("droplets",HTTP.Method.get); 301 } 302 303 /// 304 enum OceanRegion 305 { 306 ams2, 307 ams3, 308 fra1, 309 lon1, 310 nyc1, 311 nyc2, 312 nyc3, 313 sfo1, 314 sgp1, 315 tor1, 316 } 317 318 319 /// 320 immutable string[] OceanRegions; 321 /// 322 OceanRegion oceanRegion(string region) 323 { 324 OceanRegion ret; 325 auto i=OceanRegions.countUntil(region); 326 enforce(i>=0, new Exception("unknown droplet region: "~region)); 327 return cast(OceanRegion)i; 328 } 329 330 /// 331 string toString(OceanRegion region) 332 { 333 final switch(region) with(OceanRegion) 334 { 335 case ams2: 336 return "Amsterdam 2"; 337 case ams3: 338 return "Amsterdam 3"; 339 case fra1: 340 return "Frankfurt 1"; 341 case lon1: 342 return "London 1"; 343 case nyc1: 344 return "New York 1"; 345 case nyc2: 346 return "New York 2"; 347 case nyc3: 348 return "New York 3"; 349 case sfo1: 350 return "San Francisco 1"; 351 case sgp1: 352 return "Singapore 1"; 353 case tor1: 354 return "Toronto 1"; 355 } 356 assert(0); 357 } 358 359 /// 360 enum OceanDistro 361 { 362 CoreOS, 363 Debian, 364 Fedora, 365 CentOS, 366 FreeBSD, 367 Ubuntu, 368 } 369 /// 370 alias OceanImageId=Algebraic!(int,string); 371 /// 372 struct OceanGlobalImage 373 { 374 bool isApplication=false; 375 string slug; 376 int id; 377 OceanDistro distro; 378 string application; 379 } 380 /// 381 OceanGlobalImage[] OceanImages; 382 383 384 /// 385 struct Droplet 386 { 387 OceanAPI api; 388 int id; 389 390 this(OceanAPI api, int id) 391 { 392 this.api=api; 393 this.id=id; 394 } 395 396 string toString() 397 { 398 return id.to!string; 399 } 400 auto request(string uri, HTTP.Method method=HTTP.Method.get, JSONValue params=JSONValue(null)) 401 { 402 return api.request(uri,method,params); 403 } 404 405 // Create a new Droplet 406 static auto create(OceanAPI api,string name, OceanRegion region, string size, OceanImageId image, string[] sshKeys, string backups=null, 407 string ipv6=null, string privateNetworking=null, string userData=null) 408 { 409 JSONValue params; 410 params["name"]=name; 411 params["region"]=region.to!string; 412 params["size"]=size; 413 if(image.type==typeid(string)) 414 params["image"]=image.get!string; 415 else 416 params["image"]=image.get!int; 417 if (sshKeys.length>0) 418 params["ssh_keys"]=sshKeys; 419 if(backups.length>0) 420 params["backups"]=backups.to!bool; 421 if (ipv6.length>0) 422 params["ipv6"]=ipv6.to!bool; 423 if (privateNetworking.length>0) 424 params["private_networking"]=privateNetworking.to!bool; 425 if (userData.length>0) 426 params["user_data"]=userData; 427 return api.request("droplets", HTTP.Method.post, params); 428 } 429 } 430 431 /// Makes an action 432 JSONValue action(Droplet droplet, DropletAction actionType,JSONValue params=JSONValue(null)) 433 { 434 params["type"]=actionType.toString; 435 return droplet.request(format("droplets/%s/actions",droplet.id), HTTP.Method.post, params); 436 } 437 438 /// 439 enum DropletAction 440 { 441 reboot, 442 powerCycle, 443 shutdown, 444 powerOff, 445 powerOn, 446 passwordReset, 447 resize, 448 restore, 449 rebuild, 450 rename, 451 changeKernel, 452 enableIPv6, 453 disableBackups, 454 enablePrivateNetworking, 455 snapshot, 456 upgrade, 457 } 458 459 /// 460 string toString(DropletAction action) 461 { 462 final switch(action) with(DropletAction) 463 { 464 case reboot: 465 return "reboot"; 466 case powerCycle: 467 return "power_cycle"; 468 case shutdown: 469 return "shutdown"; 470 case powerOff: 471 return "power_off"; 472 case powerOn: 473 return "power_on"; 474 case passwordReset: 475 return "password_reset"; 476 case resize: 477 return "resize"; 478 case restore: 479 return "restore"; 480 case rebuild: 481 return "rebuild"; 482 case rename: 483 return "rename"; 484 case changeKernel: 485 return "change_kernel"; 486 case enableIPv6: 487 return "enable_ipv6"; 488 case disableBackups: 489 return "disable_backups"; 490 case enablePrivateNetworking: 491 return "enable_private_networking"; 492 case snapshot: 493 return "snapshot"; 494 case upgrade: 495 return "upgrade"; 496 } 497 } 498 499 500 /// 501 immutable string[] DropletActions; 502 503 /// 504 DropletAction dropletAction(string action) 505 { 506 auto i=DropletActions.countUntil(action); 507 enforce(i>=0,new Exception("unknown droplet action: "~action)); 508 return cast(DropletAction)i; 509 } 510 511 /// 512 struct OceanResult(T) 513 { 514 bool found; 515 T result; 516 } 517 518 /// find droplet ID from anme 519 OceanResult!Droplet findDroplet(OceanAPI ocean, string name) 520 { 521 import symmetry.helper.prettyjson; 522 auto ret=ocean.Droplet(-1); 523 auto dropletResults=ocean.listDroplets; 524 auto droplets="droplets" in dropletResults; 525 enforce(droplets !is null, new Exception("bad response from Digital Ocean: "~dropletResults.prettyPrint)); 526 enforce((*droplets).type==JSONType.array, new Exception 527 ("bad response from Digital Ocean: "~dropletResults.prettyPrint)); 528 (*droplets).array.each!(a=>enforce(("name" in a.object) && a.object["name"].type==JSON_TYPE.STRING)); 529 auto i=(*droplets).array.map!(a=>a.object["name"].str).array.countUntil(name); 530 if (i==-1) 531 { 532 return OceanResult!Droplet(false,ret); 533 } 534 //auto p=("id" in ((*droplets).array[i])); 535 //enforce(p !is null, new Exception 536 // ("findDroplet cannot find id in results - malformed JSON?\n"~dropletResults.prettyPrint)); 537 return OceanResult!Droplet(true,ocean.Droplet((*droplets).array[i].object["id"].integer.to!int)); 538 } 539 540 /// List all available Kernels for a Droplet 541 auto kernels(Droplet droplet) 542 { 543 return droplet.request(format("droplets/%s/kernels",droplet.id), HTTP.Method.get); 544 } 545 546 547 /// Retrieve snapshots for a Droplet 548 auto snapshots(Droplet droplet) 549 { 550 return droplet.request(format("droplets/%s/snapshots",droplet.id), HTTP.Method.get); 551 } 552 553 /// Retrieve backups for a Droplet 554 auto backups(Droplet droplet) 555 { 556 return droplet.request(format("droplets/%s/backups",droplet.id), HTTP.Method.get); 557 } 558 559 // Retrieve actions for a Droplet 560 auto actions(Droplet droplet) 561 { 562 return droplet.request(format("droplets/%s/actions",droplet.id), HTTP.Method.get); 563 } 564 565 /// Retrieve an existing Droplet by id 566 auto retrieve(Droplet droplet) 567 { 568 return droplet.request("droplets/"~droplet.id.to!string, HTTP.Method.get); 569 } 570 571 /// Delete a Droplet 572 auto del(Droplet droplet) 573 { 574 return droplet.request("droplets/"~droplet.id.to!string, HTTP.Method.del); 575 } 576 577 /// 578 auto neighbours(Droplet droplet) 579 { 580 return droplet.request(format("droplets/%s/neighbors",droplet.id),HTTP.Method.get); 581 } 582 583 /// Reboot a Droplet 584 auto reboot(Droplet droplet) 585 { 586 return droplet.action(DropletAction.reboot); 587 } 588 589 /// Power Cycle a Droplet 590 auto powerCycle(Droplet droplet) 591 { 592 return droplet.action(DropletAction.powerCycle); 593 } 594 595 /// Shutdown a Droplet 596 auto shutdown(Droplet droplet) 597 { 598 return droplet.action(DropletAction.shutdown); 599 } 600 601 /// Power Off a Droplet 602 auto powerOff(Droplet droplet) 603 { 604 return droplet.action(DropletAction.powerOff); 605 } 606 607 /// Power On a Droplet 608 auto powerOn(Droplet droplet) 609 { 610 return droplet.action(DropletAction.powerOn); 611 } 612 613 /// Password Reset a Droplet 614 auto passwordReset(Droplet droplet) 615 { 616 return droplet.action(DropletAction.passwordReset); 617 } 618 619 /// Resize a Droplet 620 auto resize(Droplet droplet, string size) 621 { 622 JSONValue params; 623 params["size"]=size; 624 return droplet.action(DropletAction.resize, params); 625 } 626 627 /// Restore a Droplet 628 auto restore(Droplet droplet, string image) 629 { 630 JSONValue params; 631 params["image"]=image; 632 return droplet.action(DropletAction.restore, params); 633 } 634 635 /// Rebuild a Droplet 636 auto rebuild(Droplet droplet, string image) 637 { 638 JSONValue params; 639 params["image"]=image; 640 return droplet.action(DropletAction.rebuild, params); 641 } 642 643 /// Rename a Droplet 644 auto rename(OceanAPI api, Droplet droplet, string name) 645 { 646 JSONValue params; 647 params["name"]=name; 648 return droplet.action(DropletAction.rename,params); 649 } 650 651 /// Change the Kernel 652 auto changeKernel(Droplet droplet, string kernel) 653 { 654 JSONValue params; 655 params["kernel"]=kernel; 656 return droplet.action(DropletAction.changeKernel, params); 657 } 658 659 /// Enable IPv6 660 auto enableIPv6(Droplet droplet) 661 { 662 return droplet.action(DropletAction.enableIPv6); 663 } 664 665 /// Disable Backups 666 auto disableBackups(Droplet droplet) 667 { 668 return droplet.action(DropletAction.disableBackups); 669 } 670 671 /// Enable Private Networking 672 auto enablePrivateNetworking(Droplet droplet) 673 { 674 return droplet.action(DropletAction.enablePrivateNetworking); 675 } 676 677 /// Snapshot 678 auto doSnapshot(Droplet droplet, string name=null) 679 { 680 JSONValue params; 681 if (name.length>0) 682 params["name"]=name; 683 return droplet.action(DropletAction.snapshot, params); 684 } 685 686 /// Retrieve a Droplet Action 687 auto retrieveAction(Droplet droplet, string actionId) 688 { 689 return droplet.request( 690 format("droplets/%s/actions/%s",droplet.id, actionId), HTTP.Method.get); 691 } 692 693 /// 694 auto upgrade(Droplet droplet) 695 { 696 JSONValue params; 697 params["upgrade"]=true; 698 droplet.action(DropletAction.upgrade,params); 699 } 700 701 /// 702 struct OceanImage 703 { 704 OceanAPI api; 705 string id; 706 alias id this; 707 this(OceanAPI api, string id) 708 { 709 this.api=api; 710 this.id=id; 711 } 712 auto request(string uri, HTTP.Method method=HTTP.Method.get, JSONValue params=JSONValue(null)) 713 { 714 return api.request(uri,method,params); 715 } 716 } 717 718 /// List all images 719 auto listImages(OceanAPI api) 720 { 721 return api.request("images", HTTP.Method.get); 722 } 723 724 /// Retrieve an existing Image by id or slug 725 auto get(OceanImage image) 726 { 727 return image.request("images/"~image.id, HTTP.Method.get); 728 } 729 730 /// Delete an Image 731 auto del(OceanImage image) 732 { 733 return image.request("images/"~image.id, HTTP.Method.del); 734 } 735 736 /// Update an Image 737 auto update(OceanImage image, string name) 738 { 739 JSONValue params; 740 params["name"]=name; 741 return image.request("images/"~image.id, HTTP.Method.put, params); 742 } 743 744 /// Transfer an Image 745 auto transfer(OceanImage image, OceanRegion region) 746 { 747 JSONValue params; 748 params["type"]="transfer"; 749 params["region"]=region.to!string; 750 return image.request(format("images/%s/actions",image.id), HTTP.Method.post,params); 751 } 752 753 /// Retrieve an existing Image Action 754 auto getImageAction(OceanImage image, string actionId) 755 { 756 return image.request(format("images/%s/actions/%s",image.id,actionId), HTTP.Method.get); 757 } 758 759 /// 760 struct OceanKey 761 { 762 OceanAPI api; 763 string value; 764 765 /// 766 this(OceanAPI api,string key) 767 { 768 this.api=api; 769 this.value=key; 770 } 771 772 /// 773 auto request(string uri, HTTP.Method method=HTTP.Method.get, JSONValue params=JSONValue(null)) 774 { 775 return api.request(uri,method,params); 776 } 777 778 /// Create a new Key 779 static auto create(OceanAPI api, string name, string publicKey) 780 { 781 JSONValue params; 782 params["name"]=name; 783 params["public_key"]=publicKey; 784 return api.request("account/keys", HTTP.Method.post, params); 785 } 786 } 787 788 /// list all keys 789 auto listKeys(OceanAPI api) 790 { 791 return api.request("account/keys", HTTP.Method.get); 792 } 793 794 795 /// Retrieve an existing Key by Id or Fingerprint 796 auto retrieve(OceanKey key) 797 { 798 return key.request("account/keys/"~key.value, HTTP.Method.get); 799 } 800 801 /// Update an existing Key by Id or Fingerprint 802 auto updateName(OceanKey key, string name) 803 { 804 JSONValue params; 805 params["name"]=name; 806 return key.request("account/keys/"~key.value, HTTP.Method.put, params); 807 } 808 809 /// Destroy an existing Key by Id or Fingerprint 810 auto del(OceanKey key) 811 { 812 return key.request("account/keys/"~key.value, HTTP.Method.del); 813 } 814 815 816 /// list all regions 817 auto listRegions(OceanAPI api) 818 { 819 return api.request("regions", HTTP.Method.get); 820 } 821 822 /// list all sizes 823 auto listSizes(OceanAPI api) 824 { 825 return api.request("sizes", HTTP.Method.get); 826 }