1 package WebService
::Scaleway
;
7 our $VERSION = '0.001001';
12 use Scalar
::Util qw
/blessed/;
14 my $ht = HTTP
::Tiny
->new(
15 agent
=> "WebService-Scaleway/$VERSION ",
19 # Instance of WebService::Scaleway with no API key
20 # Used to create tokens from email/password
22 $dummy = bless \
$dummy, __PACKAGE__
;
24 sub _account
($) { "https://account.scaleway.com$_[0]"}
25 sub _api
($) { "https://api.scaleway.com$_[0]" }
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
};
35 decode_json
$ret->{content
} if $ret->{status
} != 204;
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
=> @_) }
45 my @ret = map { bless $_, 'WebService::Scaleway::Resource' } @_;
46 wantarray ?
@ret : $ret[0]
50 my ($class, $token) = @_;
51 $token = $dummy->create_token(@_[1..$#_])->id if @_ > 2;
57 my @account_res = qw
/token organization user/;
58 my @api_res = qw
/server volume snapshot image ip security_group/;
61 map ({ $_ => _account
"/${_}s" } @account_res),
62 map { $_ => _api
"/${_}s" } @api_res);
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/],
80 for my $res (keys %res) {
81 dynsub
$res, "get_$res", sub {
82 local *__ANON__
= $res;
83 _tores
shift->_get("$res{$res}/$_[0]")->{$res}
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]
92 dynsub
"delete_$res", sub {
93 local *__ANON__
= "delete_$res";
94 shift->_delete("$res{$res}/$_[0]")
97 dynsub
"create_$res", sub {
98 local *__ANON__
= "create_$res";
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}};
105 $parms[$_] => (blessed
$_[$_] ?
$_[$_]->id : $_[$_]) } 0 .. $#_ };
107 _tores
$self->_post($res{$res}, { content
=> encode_json
$content })->{$res}
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 })
118 sub security_group_rule
{
119 _tores
shift->_get(_api
"/security_groups/$_[0]/rules/$_[1]")->{rule
}
122 sub security_group_rules
{
123 _tores @
{shift->_get(_api
"/security_groups/$_[0]/rules")->{rules
}}
127 *get_security_group_rule
= \
&security_group_rule
;
128 *list_security_group_rule
= \
&security_group_rules
;
131 sub delete_security_group_rule
{
132 shift->_delete(_api
"/security_groups/$_[0]/rules/$_[1]")
135 sub create_security_group_rule
{
139 unless (ref $content eq 'HASH') {
140 my @parms = qw
/organization action direction ip_range protocol dest_port_from/;
141 $content = { map { $parms[$_] => $_[$_] } 0 .. $#_ };
143 $self->_post(_api
"/security_groups/$grp/rules", { content
=> encode_json
$content })
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 })
152 @
{shift->_get(_api
"/servers/$_[0]/action")->{actions
}}
155 BEGIN { *list_server_actions
= \
&server_actions
}
157 sub perform_server_action
{
158 my $content = encode_json
{ action
=> $_[2] };
159 _tores
shift->_post(_api
"/servers/$_[0]/action", { content
=> $content })->{task
};
163 _tores
shift->_patch(_account
"/tokens/$_[0]")->{token
}
166 sub server_metadata
{
167 _tores
$dummy->_get('http://169.254.42.42/conf?format=json')
170 package # hide from PAUSE
171 WebService
::Scaleway
::Resource
;
173 use overload
'""' => sub { shift->id };
178 my ($attr) = $AUTOLOAD =~ m/::([^:]*)$/s;
179 die "No such attribute: $attr" unless exists $self->{$attr};
184 my ($self, $sub) = @_;
185 exists $self->{$sub} ?
sub { shift->{$sub} } : undef
188 sub DESTROY
{} # Don't call AUTOLOAD on destruction
197 WebService::Scaleway - Perl interface to Scaleway cloud server provider API
201 use WebService::Scaleway;
202 my $token = ...; # API token here
203 my $sw = WebService::Scaleway->new($token);
204 my $org = $sw->organizations;
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});
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).
215 # Change the server name
216 $srv->{name} = 'Debian';
217 $sw->update_server($srv);
220 $sw->perform_server_action($srv, 'poweron');
221 say "The server is now booting. To access it, do ssh root@", $ip->address;
225 Scaleway is an IaaS provider that offers bare metal ARM cloud servers.
226 WebService::Scaleway is a Perl interface to the Scaleway API.
230 WebService::Scaleway objects are defined by their authentication
231 token. There are two consructors:
235 =item WebService::Scaleway->B<new>(I<$auth_token>)
237 Construct a WebService::Scaleway object from a given authentication
240 =item WebService::Scaleway->B<new>(I<$email>, I<$password>)
242 Construct a WebService::Scaleway object from an authentication token
243 obtained by logging in with the given credentials.
247 =head2 Listing resources
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 >>.
255 There is no difference between B<resources>() and
260 =item $self->B<tokens>
262 =item $self->B<list_tokens>
264 Official documentation: L<https://developer.scaleway.com/#tokens-tokens-get>.
266 =item $self->B<organizations>
268 =item $self->B<list_organizations>
270 Official documentation: L<https://developer.scaleway.com/#organizations-organizations>.
272 =item $self->B<servers>
274 =item $self->B<list_servers>
276 Official documentation: L<https://developer.scaleway.com/#servers-servers-get>.
278 =item $self->B<volumes>
280 =item $self->B<list_volumes>
282 Official documentation: L<https://developer.scaleway.com/#volumes-volumes-get>.
284 =item $self->B<snapshots>
286 =item $self->B<list_snapshots>
288 Official documentation: L<https://developer.scaleway.com/#snapshots-snapshots-get>.
290 =item $self->B<images>
292 =item $self->B<list_images>
294 Official documentation: L<https://developer.scaleway.com/#images-images-get>.
298 =item $self->B<list_ips>
300 Official documentation: L<https://developer.scaleway.com/#ips-ips-get>.
302 =item $self->B<security_groups>
304 =item $self->B<list_security_groups>
306 Official documentation: L<https://developer.scaleway.com/#security-groups-security-groups-get>.
308 =item $self->B<security_group_rules>(I<$group_id>)
310 =item $self->B<list_security_group_rules>(I<$group_id>)
312 Official documentation: L<https://developer.scaleway.com/#security-groups-manage-rules-get>.
316 =head2 Retrieving resources
318 These methods take the ID of a resource and return the resource as a
319 blessed hashref as described in the previous section.
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.
325 There is no difference between B<resource>(I<$id>) and
326 B<get_resource>(I<$id>).
330 =item $self->B<token>(I<$id>)
332 =item $self->B<get_token>(I<$id>)
334 Official documentation: L<https://developer.scaleway.com/#tokens-token-get>.
336 =item $self->B<user>(I<$id>)
338 =item $self->B<get_user>(I<$id>)
340 Official documentation: L<https://developer.scaleway.com/#users-user>.
342 =item $self->B<server>(I<$id>)
344 =item $self->B<get_server>(I<$id>)
346 Official documentation: L<https://developer.scaleway.com/#servers-server-get>.
348 =item $self->B<volume>(I<$id>)
350 =item $self->B<get_volume>(I<$id>)
352 Official documentation: L<https://developer.scaleway.com/#volumes-volume-get>.
354 =item $self->B<snapshot>(I<$id>)
356 =item $self->B<get_snapshot>(I<$id>)
358 Official documentation: L<https://developer.scaleway.com/#snapshots-snapshot-get>.
360 =item $self->B<image>(I<$id>)
362 =item $self->B<get_image>(I<$id>)
364 Official documentation: L<https://developer.scaleway.com/#images-operation-on-a-single-image-get>.
366 =item $self->B<ip>(I<$id>)
368 =item $self->B<get_ip>(I<$id>)
370 Official documentation: L<https://developer.scaleway.com/#ips-ip-get>.
372 =item $self->B<security_group>(I<$id>)
374 =item $self->B<get_security_group>(I<$id>)
376 Official documentation: L<https://developer.scaleway.com/#security-groups-operation-on-a-security-groups-get>.
378 =item $self->B<security_group_rule>(I<$group_id>, I<$rule_id>)
380 =item $self->B<get_security_group_rule>(I<$group_id>, I<$rule_id>)
382 Official documentation: L<https://developer.scaleway.com/#security-groups-operation-on-a-security-rule-get>.
386 =head2 Deleting resources
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
394 =item $self->B<delete_token>(I<$id>)
396 Official documentation: L<https://developer.scaleway.com/#tokens-token-delete>.
398 =item $self->B<delete_server>(I<$id>)
400 Official documentation: L<https://developer.scaleway.com/#servers-server-delete>.
402 =item $self->B<delete_volume>(I<$id>)
404 Official documentation: L<https://developer.scaleway.com/#volumes-volume-delete>.
406 =item $self->B<delete_snapshot>(I<$id>)
408 Official documentation: L<https://developer.scaleway.com/#snapshots-snapshot-delete>.
410 =item $self->B<delete_image>(I<$id>)
412 Official documentation: L<https://developer.scaleway.com/#images-operation-on-a-single-image-delete>.
414 =item $self->B<delete_ip>(I<$id>)
416 Official documentation: L<https://developer.scaleway.com/#ips-ip-delete>.
418 =item $self->B<delete_security_group>(I<$id>)
420 Official documentation: L<https://developer.scaleway.com/#security-groups-operation-on-a-security-groups-delete>.
422 =item $self->B<delete_security_group_rule>(I<$group_id>, I<$rule_id>)
424 Official documentation: L<https://developer.scaleway.com/#security-groups-operation-on-a-security-rule-delete>.
428 =head2 Modifying resources
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">.
438 =item $self->B<update_server>(I<$resource>)
440 Official documentation: L<https://developer.scaleway.com/#servers-server-put>.
442 =item $self->B<update_snapshot>(I<$resource>)
444 Official documentation: L<https://developer.scaleway.com/#snapshots-snapshot-put>.
446 =item $self->B<update_image>(I<$resource>)
448 Official documentation: L<https://developer.scaleway.com/#images-operation-on-a-single-image-put>.
450 =item $self->B<update_ip>(I<$resource>)
452 Official documentation: L<https://developer.scaleway.com/#ips-ip-put>.
454 =item $self->B<update_security_group>(I<$resource>)
456 Official documentation: L<https://developer.scaleway.com/#security-groups-operation-on-a-security-groups-put>.
458 =item $self->B<update_security_group_rule>(I<$group_id>, I<$resource>)
460 Official documentation: L<https://developer.scaleway.com/#security-groups-operation-on-a-security-rule-put>.
464 =head2 Creating resources
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">.
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.
475 Most of these methods require an organization ID. You can obtain it
476 with the B<organizations> method described above.
480 =item $self->B<create_token>(I<\%data>)
482 =item $self->B<create_token>(I<$email>, I<$password>, [I<$expires>])
484 Authenticates a user against their username and password and returns
485 an authentication token. If I<$expires> (default: false) is true, the
488 This method is called internally by the two-argument constructor.
490 Official documentation: L<https://developer.scaleway.com/#tokens-tokens-get>.
492 =item $self->B<create_server>(I<\%data>)
494 =item $self->B<create_server>(I<$name>, I<$organization>, I<$image>, I<$volumes>, [I<$tags>])
496 Creates and returns a new server.
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
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:
511 organization => "ecc1c86a-eabb-43a7-9c0a-77e371753c0a",
512 size => 10_000_000_000,
513 volume_type => "l_sdd",
516 Note that there B<may not> be any blessed hashrefs inside I<$volumes>.
518 Official documentation: L<https://developer.scaleway.com/#servers-servers-get>.
520 =item $self->B<create_volume>(I<\%data>)
522 =item $self->B<create_volume>(I<$name>, I<$organization>, I<$volume_type>, I<$size>)
524 Creates and returns a new volume. I<$volume_type> currently must be
525 C<l_ssd>. I<$size> is the size in bytes.
527 Official documentation: L<https://developer.scaleway.com/#volumes-volumes-get>.
529 =item $self->B<create_snapshot>(I<\%data>)
531 =item $self->B<create_snapshot>(I<$name>, I<$organization>, I<$volume_id>)
533 Creates and returns a snapshot of the volume I<$volume_id>.
535 Official documentation: L<https://developer.scaleway.com/#snapshots-snapshots-get>.
537 =item $self->B<create_image>(I<\%data>)
539 =item $self->B<create_image>(I<$name>, I<$organization>, I<$root_volume>, I<$arch>)
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">).
544 Official documentation: L<https://developer.scaleway.com/#images-images-get>.
546 =item $self->B<create_ip>(I<\%data>)
548 =item $self->B<create_ip>(I<$organization>)
550 Official documentation: L<https://developer.scaleway.com/#ips-ips-get>.
552 =item $self->B<create_security_group>(I<\%data>)
554 =item $self->B<create_security_group>(I<$name>, I<$organization>, I<$description>)
556 Official documentation: L<https://developer.scaleway.com/#security-groups-security-groups-get>.
558 =item $self->B<create_security_group_rule>(I<$group_id>)
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>])
563 Official documentation: L<https://developer.scaleway.com/#security-groups-manage-rules-get>.
567 =head2 Miscellaneous methods
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 >>
577 =item $self->B<server_actions>(I<$server_id>)
579 =item $self->B<list_server_actions>(I<$server_id>)
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.
585 Official documentation: L<https://developer.scaleway.com/#servers-actions-get>
587 =item $self->B<perform_server_action>(I<$server_id>, I<$action>)
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.
593 This is not very useful, as this module does not currently offer any
594 function for tracking tasks.
596 Official documentation: L<https://developer.scaleway.com/#servers-actions-post>
598 =item $self->B<refresh_token>(I<$token_id>)
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.
603 Official documentation: L<https://developer.scaleway.com/#tokens-token-patch>
605 =item $self->B<server_metadata>
607 This method can only be called from a Scaleway server. It returns
608 information about the server as a blessed hashref.
610 Official documentation: L<https://developer.scaleway.com/#metadata-c1-server-metadata>
616 L<https://developer.scaleway.com/>
620 Marius Gavrilescu, E<lt>marius@ieval.roE<gt>
622 =head1 COPYRIGHT AND LICENSE
624 Copyright (C) 2015 by Marius Gavrilescu
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.