Bump version and update Changes
[webservice-scaleway.git] / lib / WebService / Scaleway.pm
CommitLineData
362a19d8
MG
1package WebService::Scaleway;
2
3use 5.014000;
4use strict;
5use warnings;
6
30c0c3c5 7our $VERSION = '0.001001';
362a19d8
MG
8
9use Carp qw/croak/;
10use HTTP::Tiny;
11use JSON::MaybeXS;
12use Scalar::Util qw/blessed/;
13
14my $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
21my $dummy = '';
22$dummy = bless \$dummy, __PACKAGE__;
23
24sub _account ($) { "https://account.scaleway.com$_[0]"}
25sub _api ($) { "https://api.scaleway.com$_[0]" }
26
27sub _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
38sub _get { shift->_request(GET => @_) }
39sub _post { shift->_request(POST => @_) }
40sub _patch { shift->_request(PATCH => @_) }
41sub _put { shift->_request(PUT => @_) }
42sub _delete { shift->_request(DELETE => @_) }
43
44sub _tores {
45 my @ret = map { bless $_, 'WebService::Scaleway::Resource' } @_;
46 wantarray ? @ret : $ret[0]
47}
48
49sub new {
50 my ($class, $token) = @_;
51 $token = $dummy->create_token(@_[1..$#_])->id if @_ > 2;
52
53 bless \$token, $class
54}
55
56BEGIN {
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
118sub security_group_rule {
119 _tores shift->_get(_api "/security_groups/$_[0]/rules/$_[1]")->{rule}
120}
121
122sub security_group_rules {
123 _tores @{shift->_get(_api "/security_groups/$_[0]/rules")->{rules}}
124}
125
126BEGIN {
127 *get_security_group_rule = \&security_group_rule;
128 *list_security_group_rule = \&security_group_rules;
129}
130
131sub delete_security_group_rule {
132 shift->_delete(_api "/security_groups/$_[0]/rules/$_[1]")
133}
134
135sub 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
146sub 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
151sub server_actions {
152 @{shift->_get(_api "/servers/$_[0]/action")->{actions}}
153}
154
155BEGIN { *list_server_actions = \&server_actions }
156
157sub perform_server_action {
b292204e 158 my $content = encode_json { action => $_[2] };
362a19d8
MG
159 _tores shift->_post(_api "/servers/$_[0]/action", { content => $content })->{task};
160}
161
162sub refresh_token {
163 _tores shift->_patch(_account "/tokens/$_[0]")->{token}
164}
165
166sub server_metadata {
167 _tores $dummy->_get('http://169.254.42.42/conf?format=json')
168}
169
170package # hide from PAUSE
171 WebService::Scaleway::Resource;
172
173use overload '""' => sub { shift->id };
174
175our $AUTOLOAD;
176sub 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
183sub can {
184 my ($self, $sub) = @_;
185 exists $self->{$sub} ? sub { shift->{$sub} } : undef
186}
187
188sub DESTROY {} # Don't call AUTOLOAD on destruction
189
1901;
191__END__
192
193=encoding utf-8
194
195=head1 NAME
196
197WebService::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;
d06a69ab
MG
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).
362a19d8
MG
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
225Scaleway is an IaaS provider that offers bare metal ARM cloud servers.
226WebService::Scaleway is a Perl interface to the Scaleway API.
227
228=head2 Constructors
229
230WebService::Scaleway objects are defined by their authentication
231token. There are two consructors:
232
233=over
234
235=item WebService::Scaleway->B<new>(I<$auth_token>)
236
237Construct a WebService::Scaleway object from a given authentication
238token.
239
240=item WebService::Scaleway->B<new>(I<$email>, I<$password>)
241
242Construct a WebService::Scaleway object from an authentication token
243obtained by logging in with the given credentials.
244
245=back
246
247=head2 Listing resources
248
249These methods return a list of all resources of a given type
250associated to your account. Each resource is a blessed hashref with
251C<AUTOLOAD>ed accessors (for example C<< $resource->{name} >> can be
252written as C<< $resource->name >>) and that stringifies to the ID of
253the resource: C<< $resource->id >>.
254
255There is no difference between B<resources>() and
256B<list_resources>().
257
258=over
259
260=item $self->B<tokens>
261
262=item $self->B<list_tokens>
263
264Official documentation: L<https://developer.scaleway.com/#tokens-tokens-get>.
265
266=item $self->B<organizations>
267
268=item $self->B<list_organizations>
269
270Official documentation: L<https://developer.scaleway.com/#organizations-organizations>.
271
272=item $self->B<servers>
273
274=item $self->B<list_servers>
275
276Official documentation: L<https://developer.scaleway.com/#servers-servers-get>.
277
278=item $self->B<volumes>
279
280=item $self->B<list_volumes>
281
282Official documentation: L<https://developer.scaleway.com/#volumes-volumes-get>.
283
284=item $self->B<snapshots>
285
286=item $self->B<list_snapshots>
287
288Official documentation: L<https://developer.scaleway.com/#snapshots-snapshots-get>.
289
290=item $self->B<images>
291
292=item $self->B<list_images>
293
294Official documentation: L<https://developer.scaleway.com/#images-images-get>.
295
296=item $self->B<ips>
297
298=item $self->B<list_ips>
299
300Official 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
306Official 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
312Official documentation: L<https://developer.scaleway.com/#security-groups-manage-rules-get>.
313
314=back
315
316=head2 Retrieving resources
317
d06a69ab 318These methods take the ID of a resource and return the resource as a
362a19d8
MG
319blessed hashref as described in the previous section.
320
d06a69ab
MG
321You can pass a blessed hashref instead of a resource ID, and you'll
322get a fresh version of the object passed. Useful if something updated
323the object in the meantime.
324
362a19d8
MG
325There is no difference between B<resource>(I<$id>) and
326B<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
334Official 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
340Official 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
346Official 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
352Official 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
358Official 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
364Official 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
370Official 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
376Official 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
382Official documentation: L<https://developer.scaleway.com/#security-groups-operation-on-a-security-rule-get>.
383
384=back
385
386=head2 Deleting resources
387
d06a69ab
MG
388These methods take the ID of a resource and delete it. They do not
389return anything. You can pass a blessed hashref instead of a resource
390ID.
362a19d8
MG
391
392=over
393
394=item $self->B<delete_token>(I<$id>)
395
396Official documentation: L<https://developer.scaleway.com/#tokens-token-delete>.
397
398=item $self->B<delete_server>(I<$id>)
399
400Official documentation: L<https://developer.scaleway.com/#servers-server-delete>.
401
402=item $self->B<delete_volume>(I<$id>)
403
404Official documentation: L<https://developer.scaleway.com/#volumes-volume-delete>.
405
406=item $self->B<delete_snapshot>(I<$id>)
407
408Official documentation: L<https://developer.scaleway.com/#snapshots-snapshot-delete>.
409
410=item $self->B<delete_image>(I<$id>)
411
412Official documentation: L<https://developer.scaleway.com/#images-operation-on-a-single-image-delete>.
413
414=item $self->B<delete_ip>(I<$id>)
415
416Official documentation: L<https://developer.scaleway.com/#ips-ip-delete>.
417
418=item $self->B<delete_security_group>(I<$id>)
419
420Official 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
424Official documentation: L<https://developer.scaleway.com/#security-groups-operation-on-a-security-rule-delete>.
425
426=back
427
428=head2 Modifying resources
429
430These methods take a hashref representing a resource that already
431exists and update it. The value of C<< $resource->{id} >> is used for
432identifying this resource on the remote end. Both blessed and
433unblessed hashrefs are accepted. The updated resource is returned as a
434blessed hashref as described in L</"Listing resources">.
435
436=over
437
438=item $self->B<update_server>(I<$resource>)
439
440Official documentation: L<https://developer.scaleway.com/#servers-server-put>.
441
442=item $self->B<update_snapshot>(I<$resource>)
443
444Official documentation: L<https://developer.scaleway.com/#snapshots-snapshot-put>.
445
446=item $self->B<update_image>(I<$resource>)
447
448Official documentation: L<https://developer.scaleway.com/#images-operation-on-a-single-image-put>.
449
450=item $self->B<update_ip>(I<$resource>)
451
452Official documentation: L<https://developer.scaleway.com/#ips-ip-put>.
453
454=item $self->B<update_security_group>(I<$resource>)
455
456Official 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
460Official documentation: L<https://developer.scaleway.com/#security-groups-operation-on-a-security-rule-put>.
461
462=back
463
464=head2 Creating resources
465
466These methods take either a hash that is passed directly to the API or
467a method-specific list of positional parameters. They create a new
468resource and return it as a blessed hashref as described in
469L</"Listing resources">.
470
471When using positional parameters, you can pass a resource in blessed
d06a69ab
MG
472hashref format where a resource ID is expected, unless the method's
473documentation says otherwise.
362a19d8
MG
474
475Most of these methods require an organization ID. You can obtain it
476with 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
484Authenticates a user against their username and password and returns
485an authentication token. If I<$expires> (default: false) is true, the
486token will expire.
487
488This method is called internally by the two-argument constructor.
489
490Official 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
496Creates and returns a new server.
497
498I<$name> is the server name. I<$organization> is the organization ID.
499I<$image> is the image ID. I<$volumes> is a "sparse array" (hashref
d06a69ab
MG
500from indexes to volume IDs, indexed from 1) of B<extra> volumes (that
501is, volumes other than the root volume). I<$tags> is an optional
502arrayref of tags.
503
504For the I<$volumes> parameter you can pass hashrefs that describe
505volumes instead of volume IDs. This will create new volumes. The
506hashrefs are (presumably) passed to B<create_volume>. An example
507inspired 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
516Note that there B<may not> be any blessed hashrefs inside I<$volumes>.
362a19d8
MG
517
518Official 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
524Creates and returns a new volume. I<$volume_type> currently must be
525C<l_ssd>. I<$size> is the size in bytes.
526
527Official 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
533Creates and returns a snapshot of the volume I<$volume_id>.
534
535Official 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
541Creates and returns an image from the volume I<$root_volume>. I<$arch>
542is the architecture of the image (currently must be C<"arm">).
543
544Official 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
550Official 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
556Official 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
563Official documentation: L<https://developer.scaleway.com/#security-groups-manage-rules-get>.
564
565=back
566
567=head2 Miscellaneous methods
568
569These are methods that don't fit any previous category. Any use of
570"blessed hashref" refers to the concept described in L</"Listing
571resources">. Wherever a resource ID is expected, you can instead pass
572a resource as a blessed hashref and the method will call C<< ->id >>
573on 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
581Returns a list of strings representing possible actions you can
582perform on the given server. Example actions are powering on/off a
583server or rebooting it.
584
585Official documentation: L<https://developer.scaleway.com/#servers-actions-get>
586
587=item $self->B<perform_server_action>(I<$server_id>, I<$action>)
588
589Performs an action on a server. I<$action> is one of the strings
590returned by B<server_actions>. The function returns a blessed hashref
591with information about the task.
592
593This is not very useful, as this module does not currently offer any
594function for tracking tasks.
595
596Official documentation: L<https://developer.scaleway.com/#servers-actions-post>
597
598=item $self->B<refresh_token>(I<$token_id>)
599
600This method takes the ID of an expirable token, extends its expiration
601date by 30 minutes, and returns the new token as a blessed hashref.
602
603Official documentation: L<https://developer.scaleway.com/#tokens-token-patch>
604
605=item $self->B<server_metadata>
606
607This method can only be called from a Scaleway server. It returns
608information about the server as a blessed hashref.
609
610Official documentation: L<https://developer.scaleway.com/#metadata-c1-server-metadata>
611
612=back
613
614=head1 SEE ALSO
615
616L<https://developer.scaleway.com/>
617
618=head1 AUTHOR
619
620Marius Gavrilescu, E<lt>marius@ieval.roE<gt>
621
622=head1 COPYRIGHT AND LICENSE
623
624Copyright (C) 2015 by Marius Gavrilescu
625
626This library is free software; you can redistribute it and/or modify
627it under the same terms as Perl itself, either Perl version 5.20.2 or,
628at your option, any later version of Perl 5 you may have available.
629
630
631=cut
This page took 0.043312 seconds and 4 git commands to generate.