Bump version and update Changes
[webservice-scaleway.git] / lib / WebService / Scaleway.pm
1 package WebService::Scaleway;
2
3 use 5.014000;
4 use strict;
5 use warnings;
6
7 our $VERSION = '0.001001';
8
9 use Carp qw/croak/;
10 use HTTP::Tiny;
11 use JSON::MaybeXS;
12 use Scalar::Util qw/blessed/;
13
14 my $ht = HTTP::Tiny->new(
15 agent => "WebService-Scaleway/$VERSION ",
16 verify_SSL => 1,
17 );
18
19 # Instance of WebService::Scaleway with no API key
20 # Used to create tokens from email/password
21 my $dummy = '';
22 $dummy = bless \$dummy, __PACKAGE__;
23
24 sub _account ($) { "https://account.scaleway.com$_[0]"}
25 sub _api ($) { "https://api.scaleway.com$_[0]" }
26
27 sub _request {
28 my ($self, $method, $url, $opts) = @_;
29 $opts->{headers} //= {};
30 $opts->{headers}{'X-Auth-Token'} = $$self if $$self;
31 $opts->{headers}{'Content-Type'} = 'application/json';
32 my $ret = $ht->request($method, $url, $opts);
33 die 'Request to Scaleway API server was unsuccessful: ' . $ret->{status} . ' ' . $ret->{reason} . '; ' . $ret->{content} unless $ret->{success};
34
35 decode_json $ret->{content} if $ret->{status} != 204;
36 }
37
38 sub _get { shift->_request(GET => @_) }
39 sub _post { shift->_request(POST => @_) }
40 sub _patch { shift->_request(PATCH => @_) }
41 sub _put { shift->_request(PUT => @_) }
42 sub _delete { shift->_request(DELETE => @_) }
43
44 sub _tores {
45 my @ret = map { bless $_, 'WebService::Scaleway::Resource' } @_;
46 wantarray ? @ret : $ret[0]
47 }
48
49 sub new {
50 my ($class, $token) = @_;
51 $token = $dummy->create_token(@_[1..$#_])->id if @_ > 2;
52
53 bless \$token, $class
54 }
55
56 BEGIN {
57 my @account_res = qw/token organization user/;
58 my @api_res = qw/server volume snapshot image ip security_group/;
59
60 my %res = (
61 map ({ $_ => _account "/${_}s" } @account_res),
62 map { $_ => _api "/${_}s" } @api_res);
63
64 my %create_parms = (
65 token => [qw/email password expires/],
66 server => [qw/name organization image volumes tags/],
67 volume => [qw/name organization volume_type size/],
68 snapshot => [qw/name organization volume_id/],
69 image => [qw/name organization root_volume arch/],
70 ip => [qw/ organization/],
71 security_group => [qw/name organization description/],
72 );
73
74 sub dynsub {
75 no strict 'refs';
76 my $sub = pop;
77 *$_ = $sub for @_
78 }
79
80 for my $res (keys %res) {
81 dynsub $res, "get_$res", sub {
82 local *__ANON__ = $res;
83 _tores shift->_get("$res{$res}/$_[0]")->{$res}
84 };
85
86 dynsub $res.'s', "list_$res".'s', sub {
87 local *__ANON__ = $res.'s';
88 my @ret = _tores @{shift->_get($res{$res})->{$res.'s'}};
89 wantarray ? @ret : $ret[0]
90 };
91
92 dynsub "delete_$res", sub {
93 local *__ANON__ = "delete_$res";
94 shift->_delete("$res{$res}/$_[0]")
95 };
96
97 dynsub "create_$res", sub {
98 local *__ANON__ = "create_$res";
99 my $self = shift;
100 my $content = $_[0];
101 if (blessed $content || ref $content ne 'HASH') {
102 croak "create_$res does not understand positional parameters, pass a hashref instead\n" unless $create_parms{$res};
103 my @parms = @{$create_parms{$res}};
104 $content = { map {
105 $parms[$_] => (blessed $_[$_] ? $_[$_]->id : $_[$_]) } 0 .. $#_ };
106 }
107 _tores $self->_post($res{$res}, { content => encode_json $content })->{$res}
108 };
109
110 dynsub "update_$res", sub {
111 local *__ANON__ = "update_$res";
112 my $data = blessed $_[1] ? {%{$_[1]}} : $_[1];
113 shift->_put("$res{$res}/".$data->{id}, { content => encode_json $data })
114 };
115 }
116 }
117
118 sub security_group_rule {
119 _tores shift->_get(_api "/security_groups/$_[0]/rules/$_[1]")->{rule}
120 }
121
122 sub security_group_rules {
123 _tores @{shift->_get(_api "/security_groups/$_[0]/rules")->{rules}}
124 }
125
126 BEGIN {
127 *get_security_group_rule = \&security_group_rule;
128 *list_security_group_rule = \&security_group_rules;
129 }
130
131 sub delete_security_group_rule {
132 shift->_delete(_api "/security_groups/$_[0]/rules/$_[1]")
133 }
134
135 sub create_security_group_rule {
136 my $self = shift;
137 my $grp = shift;
138 my $content = $_[0];
139 unless (ref $content eq 'HASH') {
140 my @parms = qw/organization action direction ip_range protocol dest_port_from/;
141 $content = { map { $parms[$_] => $_[$_] } 0 .. $#_ };
142 }
143 $self->_post(_api "/security_groups/$grp/rules", { content => encode_json $content })
144 }
145
146 sub update_security_group_rule {
147 my $data = blessed $_[2] ? {%{$_[2]}} : $_[2];
148 shift->_put (_api "/security_groups/$_[0]/rules/".$data->{id}, { content => encode_json $data })
149 }
150
151 sub server_actions {
152 @{shift->_get(_api "/servers/$_[0]/action")->{actions}}
153 }
154
155 BEGIN { *list_server_actions = \&server_actions }
156
157 sub perform_server_action {
158 my $content = encode_json { action => $_[2] };
159 _tores shift->_post(_api "/servers/$_[0]/action", { content => $content })->{task};
160 }
161
162 sub refresh_token {
163 _tores shift->_patch(_account "/tokens/$_[0]")->{token}
164 }
165
166 sub server_metadata {
167 _tores $dummy->_get('http://169.254.42.42/conf?format=json')
168 }
169
170 package # hide from PAUSE
171 WebService::Scaleway::Resource;
172
173 use overload '""' => sub { shift->id };
174
175 our $AUTOLOAD;
176 sub AUTOLOAD {
177 my ($self) = @_;
178 my ($attr) = $AUTOLOAD =~ m/::([^:]*)$/s;
179 die "No such attribute: $attr" unless exists $self->{$attr};
180 $self->{$attr}
181 }
182
183 sub can {
184 my ($self, $sub) = @_;
185 exists $self->{$sub} ? sub { shift->{$sub} } : undef
186 }
187
188 sub DESTROY {} # Don't call AUTOLOAD on destruction
189
190 1;
191 __END__
192
193 =encoding utf-8
194
195 =head1 NAME
196
197 WebService::Scaleway - Perl interface to Scaleway cloud server provider API
198
199 =head1 SYNOPSIS
200
201 use WebService::Scaleway;
202 my $token = ...; # API token here
203 my $sw = WebService::Scaleway->new($token);
204 my $org = $sw->organizations;
205
206 # Create an IP, a volume, and use them for a new Debian Jessie server
207 my $ip = $sw->create_ip($org);
208 my $vol = $sw->create_volume('testvol', $org, 'l_ssd', 50_000_000_000);
209 my ($debian) = grep { $_->name =~ /debian jessie/i } $sw->images;
210 my $srv = $sw->create_server('testsrv', $org, $debian, {1 => $vol->id});
211
212 # Now we have a server, an IP, and two volumes (the root volume with
213 # Debian Jessie, and the extra volume we just created).
214
215 # Change the server name
216 $srv->{name} = 'Debian';
217 $sw->update_server($srv);
218
219 # Boot the server
220 $sw->perform_server_action($srv, 'poweron');
221 say "The server is now booting. To access it, do ssh root@", $ip->address;
222
223 =head1 DESCRIPTION
224
225 Scaleway is an IaaS provider that offers bare metal ARM cloud servers.
226 WebService::Scaleway is a Perl interface to the Scaleway API.
227
228 =head2 Constructors
229
230 WebService::Scaleway objects are defined by their authentication
231 token. There are two consructors:
232
233 =over
234
235 =item WebService::Scaleway->B<new>(I<$auth_token>)
236
237 Construct a WebService::Scaleway object from a given authentication
238 token.
239
240 =item WebService::Scaleway->B<new>(I<$email>, I<$password>)
241
242 Construct a WebService::Scaleway object from an authentication token
243 obtained by logging in with the given credentials.
244
245 =back
246
247 =head2 Listing resources
248
249 These methods return a list of all resources of a given type
250 associated to your account. Each resource is a blessed hashref with
251 C<AUTOLOAD>ed accessors (for example C<< $resource->{name} >> can be
252 written as C<< $resource->name >>) and that stringifies to the ID of
253 the resource: C<< $resource->id >>.
254
255 There is no difference between B<resources>() and
256 B<list_resources>().
257
258 =over
259
260 =item $self->B<tokens>
261
262 =item $self->B<list_tokens>
263
264 Official documentation: L<https://developer.scaleway.com/#tokens-tokens-get>.
265
266 =item $self->B<organizations>
267
268 =item $self->B<list_organizations>
269
270 Official documentation: L<https://developer.scaleway.com/#organizations-organizations>.
271
272 =item $self->B<servers>
273
274 =item $self->B<list_servers>
275
276 Official documentation: L<https://developer.scaleway.com/#servers-servers-get>.
277
278 =item $self->B<volumes>
279
280 =item $self->B<list_volumes>
281
282 Official documentation: L<https://developer.scaleway.com/#volumes-volumes-get>.
283
284 =item $self->B<snapshots>
285
286 =item $self->B<list_snapshots>
287
288 Official documentation: L<https://developer.scaleway.com/#snapshots-snapshots-get>.
289
290 =item $self->B<images>
291
292 =item $self->B<list_images>
293
294 Official documentation: L<https://developer.scaleway.com/#images-images-get>.
295
296 =item $self->B<ips>
297
298 =item $self->B<list_ips>
299
300 Official documentation: L<https://developer.scaleway.com/#ips-ips-get>.
301
302 =item $self->B<security_groups>
303
304 =item $self->B<list_security_groups>
305
306 Official documentation: L<https://developer.scaleway.com/#security-groups-security-groups-get>.
307
308 =item $self->B<security_group_rules>(I<$group_id>)
309
310 =item $self->B<list_security_group_rules>(I<$group_id>)
311
312 Official documentation: L<https://developer.scaleway.com/#security-groups-manage-rules-get>.
313
314 =back
315
316 =head2 Retrieving resources
317
318 These methods take the ID of a resource and return the resource as a
319 blessed hashref as described in the previous section.
320
321 You can pass a blessed hashref instead of a resource ID, and you'll
322 get a fresh version of the object passed. Useful if something updated
323 the object in the meantime.
324
325 There is no difference between B<resource>(I<$id>) and
326 B<get_resource>(I<$id>).
327
328 =over
329
330 =item $self->B<token>(I<$id>)
331
332 =item $self->B<get_token>(I<$id>)
333
334 Official documentation: L<https://developer.scaleway.com/#tokens-token-get>.
335
336 =item $self->B<user>(I<$id>)
337
338 =item $self->B<get_user>(I<$id>)
339
340 Official documentation: L<https://developer.scaleway.com/#users-user>.
341
342 =item $self->B<server>(I<$id>)
343
344 =item $self->B<get_server>(I<$id>)
345
346 Official documentation: L<https://developer.scaleway.com/#servers-server-get>.
347
348 =item $self->B<volume>(I<$id>)
349
350 =item $self->B<get_volume>(I<$id>)
351
352 Official documentation: L<https://developer.scaleway.com/#volumes-volume-get>.
353
354 =item $self->B<snapshot>(I<$id>)
355
356 =item $self->B<get_snapshot>(I<$id>)
357
358 Official documentation: L<https://developer.scaleway.com/#snapshots-snapshot-get>.
359
360 =item $self->B<image>(I<$id>)
361
362 =item $self->B<get_image>(I<$id>)
363
364 Official documentation: L<https://developer.scaleway.com/#images-operation-on-a-single-image-get>.
365
366 =item $self->B<ip>(I<$id>)
367
368 =item $self->B<get_ip>(I<$id>)
369
370 Official documentation: L<https://developer.scaleway.com/#ips-ip-get>.
371
372 =item $self->B<security_group>(I<$id>)
373
374 =item $self->B<get_security_group>(I<$id>)
375
376 Official documentation: L<https://developer.scaleway.com/#security-groups-operation-on-a-security-groups-get>.
377
378 =item $self->B<security_group_rule>(I<$group_id>, I<$rule_id>)
379
380 =item $self->B<get_security_group_rule>(I<$group_id>, I<$rule_id>)
381
382 Official documentation: L<https://developer.scaleway.com/#security-groups-operation-on-a-security-rule-get>.
383
384 =back
385
386 =head2 Deleting resources
387
388 These methods take the ID of a resource and delete it. They do not
389 return anything. You can pass a blessed hashref instead of a resource
390 ID.
391
392 =over
393
394 =item $self->B<delete_token>(I<$id>)
395
396 Official documentation: L<https://developer.scaleway.com/#tokens-token-delete>.
397
398 =item $self->B<delete_server>(I<$id>)
399
400 Official documentation: L<https://developer.scaleway.com/#servers-server-delete>.
401
402 =item $self->B<delete_volume>(I<$id>)
403
404 Official documentation: L<https://developer.scaleway.com/#volumes-volume-delete>.
405
406 =item $self->B<delete_snapshot>(I<$id>)
407
408 Official documentation: L<https://developer.scaleway.com/#snapshots-snapshot-delete>.
409
410 =item $self->B<delete_image>(I<$id>)
411
412 Official documentation: L<https://developer.scaleway.com/#images-operation-on-a-single-image-delete>.
413
414 =item $self->B<delete_ip>(I<$id>)
415
416 Official documentation: L<https://developer.scaleway.com/#ips-ip-delete>.
417
418 =item $self->B<delete_security_group>(I<$id>)
419
420 Official documentation: L<https://developer.scaleway.com/#security-groups-operation-on-a-security-groups-delete>.
421
422 =item $self->B<delete_security_group_rule>(I<$group_id>, I<$rule_id>)
423
424 Official documentation: L<https://developer.scaleway.com/#security-groups-operation-on-a-security-rule-delete>.
425
426 =back
427
428 =head2 Modifying resources
429
430 These methods take a hashref representing a resource that already
431 exists and update it. The value of C<< $resource->{id} >> is used for
432 identifying this resource on the remote end. Both blessed and
433 unblessed hashrefs are accepted. The updated resource is returned as a
434 blessed hashref as described in L</"Listing resources">.
435
436 =over
437
438 =item $self->B<update_server>(I<$resource>)
439
440 Official documentation: L<https://developer.scaleway.com/#servers-server-put>.
441
442 =item $self->B<update_snapshot>(I<$resource>)
443
444 Official documentation: L<https://developer.scaleway.com/#snapshots-snapshot-put>.
445
446 =item $self->B<update_image>(I<$resource>)
447
448 Official documentation: L<https://developer.scaleway.com/#images-operation-on-a-single-image-put>.
449
450 =item $self->B<update_ip>(I<$resource>)
451
452 Official documentation: L<https://developer.scaleway.com/#ips-ip-put>.
453
454 =item $self->B<update_security_group>(I<$resource>)
455
456 Official documentation: L<https://developer.scaleway.com/#security-groups-operation-on-a-security-groups-put>.
457
458 =item $self->B<update_security_group_rule>(I<$group_id>, I<$resource>)
459
460 Official documentation: L<https://developer.scaleway.com/#security-groups-operation-on-a-security-rule-put>.
461
462 =back
463
464 =head2 Creating resources
465
466 These methods take either a hash that is passed directly to the API or
467 a method-specific list of positional parameters. They create a new
468 resource and return it as a blessed hashref as described in
469 L</"Listing resources">.
470
471 When using positional parameters, you can pass a resource in blessed
472 hashref format where a resource ID is expected, unless the method's
473 documentation says otherwise.
474
475 Most of these methods require an organization ID. You can obtain it
476 with the B<organizations> method described above.
477
478 =over
479
480 =item $self->B<create_token>(I<\%data>)
481
482 =item $self->B<create_token>(I<$email>, I<$password>, [I<$expires>])
483
484 Authenticates a user against their username and password and returns
485 an authentication token. If I<$expires> (default: false) is true, the
486 token will expire.
487
488 This method is called internally by the two-argument constructor.
489
490 Official documentation: L<https://developer.scaleway.com/#tokens-tokens-get>.
491
492 =item $self->B<create_server>(I<\%data>)
493
494 =item $self->B<create_server>(I<$name>, I<$organization>, I<$image>, I<$volumes>, [I<$tags>])
495
496 Creates and returns a new server.
497
498 I<$name> is the server name. I<$organization> is the organization ID.
499 I<$image> is the image ID. I<$volumes> is a "sparse array" (hashref
500 from indexes to volume IDs, indexed from 1) of B<extra> volumes (that
501 is, volumes other than the root volume). I<$tags> is an optional
502 arrayref of tags.
503
504 For the I<$volumes> parameter you can pass hashrefs that describe
505 volumes instead of volume IDs. This will create new volumes. The
506 hashrefs are (presumably) passed to B<create_volume>. An example
507 inspired by the official documentation:
508
509 $volumes = { 1 => {
510 name => "vol_demo",
511 organization => "ecc1c86a-eabb-43a7-9c0a-77e371753c0a",
512 size => 10_000_000_000,
513 volume_type => "l_sdd",
514 }};
515
516 Note that there B<may not> be any blessed hashrefs inside I<$volumes>.
517
518 Official documentation: L<https://developer.scaleway.com/#servers-servers-get>.
519
520 =item $self->B<create_volume>(I<\%data>)
521
522 =item $self->B<create_volume>(I<$name>, I<$organization>, I<$volume_type>, I<$size>)
523
524 Creates and returns a new volume. I<$volume_type> currently must be
525 C<l_ssd>. I<$size> is the size in bytes.
526
527 Official documentation: L<https://developer.scaleway.com/#volumes-volumes-get>.
528
529 =item $self->B<create_snapshot>(I<\%data>)
530
531 =item $self->B<create_snapshot>(I<$name>, I<$organization>, I<$volume_id>)
532
533 Creates and returns a snapshot of the volume I<$volume_id>.
534
535 Official documentation: L<https://developer.scaleway.com/#snapshots-snapshots-get>.
536
537 =item $self->B<create_image>(I<\%data>)
538
539 =item $self->B<create_image>(I<$name>, I<$organization>, I<$root_volume>, I<$arch>)
540
541 Creates and returns an image from the volume I<$root_volume>. I<$arch>
542 is the architecture of the image (currently must be C<"arm">).
543
544 Official documentation: L<https://developer.scaleway.com/#images-images-get>.
545
546 =item $self->B<create_ip>(I<\%data>)
547
548 =item $self->B<create_ip>(I<$organization>)
549
550 Official documentation: L<https://developer.scaleway.com/#ips-ips-get>.
551
552 =item $self->B<create_security_group>(I<\%data>)
553
554 =item $self->B<create_security_group>(I<$name>, I<$organization>, I<$description>)
555
556 Official documentation: L<https://developer.scaleway.com/#security-groups-security-groups-get>.
557
558 =item $self->B<create_security_group_rule>(I<$group_id>)
559
560 =item $self->B<create_security_group_rule>(I<$group_id>, I<$organization>, I<$action>, I<$direction>, I<$ip_range>, I<$protocol>, [<$dest_port_from>])
561
562
563 Official documentation: L<https://developer.scaleway.com/#security-groups-manage-rules-get>.
564
565 =back
566
567 =head2 Miscellaneous methods
568
569 These are methods that don't fit any previous category. Any use of
570 "blessed hashref" refers to the concept described in L</"Listing
571 resources">. Wherever a resource ID is expected, you can instead pass
572 a resource as a blessed hashref and the method will call C<< ->id >>
573 on it for you.
574
575 =over
576
577 =item $self->B<server_actions>(I<$server_id>)
578
579 =item $self->B<list_server_actions>(I<$server_id>)
580
581 Returns a list of strings representing possible actions you can
582 perform on the given server. Example actions are powering on/off a
583 server or rebooting it.
584
585 Official documentation: L<https://developer.scaleway.com/#servers-actions-get>
586
587 =item $self->B<perform_server_action>(I<$server_id>, I<$action>)
588
589 Performs an action on a server. I<$action> is one of the strings
590 returned by B<server_actions>. The function returns a blessed hashref
591 with information about the task.
592
593 This is not very useful, as this module does not currently offer any
594 function for tracking tasks.
595
596 Official documentation: L<https://developer.scaleway.com/#servers-actions-post>
597
598 =item $self->B<refresh_token>(I<$token_id>)
599
600 This method takes the ID of an expirable token, extends its expiration
601 date by 30 minutes, and returns the new token as a blessed hashref.
602
603 Official documentation: L<https://developer.scaleway.com/#tokens-token-patch>
604
605 =item $self->B<server_metadata>
606
607 This method can only be called from a Scaleway server. It returns
608 information about the server as a blessed hashref.
609
610 Official documentation: L<https://developer.scaleway.com/#metadata-c1-server-metadata>
611
612 =back
613
614 =head1 SEE ALSO
615
616 L<https://developer.scaleway.com/>
617
618 =head1 AUTHOR
619
620 Marius Gavrilescu, E<lt>marius@ieval.roE<gt>
621
622 =head1 COPYRIGHT AND LICENSE
623
624 Copyright (C) 2015 by Marius Gavrilescu
625
626 This library is free software; you can redistribute it and/or modify
627 it under the same terms as Perl itself, either Perl version 5.20.2 or,
628 at your option, any later version of Perl 5 you may have available.
629
630
631 =cut
This page took 0.055825 seconds and 4 git commands to generate.