| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package WebService::Scaleway; |
|
2
|
|
|
|
|
|
|
|
|
3
|
2
|
|
|
2
|
|
25740
|
use 5.014000; |
|
|
2
|
|
|
|
|
7
|
|
|
4
|
2
|
|
|
2
|
|
10
|
use strict; |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
55
|
|
|
5
|
2
|
|
|
2
|
|
10
|
use warnings; |
|
|
2
|
|
|
|
|
7
|
|
|
|
2
|
|
|
|
|
98
|
|
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = '0.001001'; |
|
8
|
|
|
|
|
|
|
|
|
9
|
2
|
|
|
2
|
|
8
|
use Carp qw/croak/; |
|
|
2
|
|
|
|
|
2
|
|
|
|
2
|
|
|
|
|
143
|
|
|
10
|
2
|
|
|
2
|
|
2320
|
use HTTP::Tiny; |
|
|
2
|
|
|
|
|
145034
|
|
|
|
2
|
|
|
|
|
83
|
|
|
11
|
2
|
|
|
2
|
|
1671
|
use JSON::MaybeXS; |
|
|
2
|
|
|
|
|
252767
|
|
|
|
2
|
|
|
|
|
139
|
|
|
12
|
2
|
|
|
2
|
|
19
|
use Scalar::Util qw/blessed/; |
|
|
2
|
|
|
|
|
5
|
|
|
|
2
|
|
|
|
|
1373
|
|
|
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
|
6
|
|
|
6
|
|
25
|
sub _account ($) { "https://account.scaleway.com$_[0]"} |
|
25
|
12
|
|
|
12
|
|
50
|
sub _api ($) { "https://api.scaleway.com$_[0]" } |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
sub _request { |
|
28
|
0
|
|
|
0
|
|
0
|
my ($self, $method, $url, $opts) = @_; |
|
29
|
0
|
|
0
|
|
|
0
|
$opts->{headers} //= {}; |
|
30
|
0
|
0
|
|
|
|
0
|
$opts->{headers}{'X-Auth-Token'} = $$self if $$self; |
|
31
|
0
|
|
|
|
|
0
|
$opts->{headers}{'Content-Type'} = 'application/json'; |
|
32
|
0
|
|
|
|
|
0
|
my $ret = $ht->request($method, $url, $opts); |
|
33
|
0
|
0
|
|
|
|
0
|
die 'Request to Scaleway API server was unsuccessful: ' . $ret->{status} . ' ' . $ret->{reason} . '; ' . $ret->{content} unless $ret->{success}; |
|
34
|
|
|
|
|
|
|
|
|
35
|
0
|
0
|
|
|
|
0
|
decode_json $ret->{content} if $ret->{status} != 204; |
|
36
|
|
|
|
|
|
|
} |
|
37
|
|
|
|
|
|
|
|
|
38
|
0
|
|
|
0
|
|
0
|
sub _get { shift->_request(GET => @_) } |
|
39
|
0
|
|
|
0
|
|
0
|
sub _post { shift->_request(POST => @_) } |
|
40
|
0
|
|
|
0
|
|
0
|
sub _patch { shift->_request(PATCH => @_) } |
|
41
|
0
|
|
|
0
|
|
0
|
sub _put { shift->_request(PUT => @_) } |
|
42
|
0
|
|
|
0
|
|
0
|
sub _delete { shift->_request(DELETE => @_) } |
|
43
|
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
sub _tores { |
|
45
|
0
|
|
|
0
|
|
0
|
my @ret = map { bless $_, 'WebService::Scaleway::Resource' } @_; |
|
|
0
|
|
|
|
|
0
|
|
|
46
|
0
|
0
|
|
|
|
0
|
wantarray ? @ret : $ret[0] |
|
47
|
|
|
|
|
|
|
} |
|
48
|
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
sub new { |
|
50
|
0
|
|
|
0
|
1
|
0
|
my ($class, $token) = @_; |
|
51
|
0
|
0
|
|
|
|
0
|
$token = $dummy->create_token(@_[1..$#_])->id if @_ > 2; |
|
52
|
|
|
|
|
|
|
|
|
53
|
0
|
|
|
|
|
0
|
bless \$token, $class |
|
54
|
|
|
|
|
|
|
} |
|
55
|
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
BEGIN { |
|
57
|
2
|
|
|
2
|
|
12
|
my @account_res = qw/token organization user/; |
|
58
|
2
|
|
|
|
|
6
|
my @api_res = qw/server volume snapshot image ip security_group/; |
|
59
|
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
my %res = ( |
|
61
|
6
|
|
|
|
|
35
|
map ({ $_ => _account "/${_}s" } @account_res), |
|
62
|
2
|
|
|
|
|
4
|
map { $_ => _api "/${_}s" } @api_res); |
|
|
12
|
|
|
|
|
27
|
|
|
63
|
|
|
|
|
|
|
|
|
64
|
2
|
|
|
|
|
16
|
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
|
2
|
|
|
2
|
|
14
|
no strict 'refs'; |
|
|
2
|
|
|
|
|
3
|
|
|
|
2
|
|
|
|
|
1167
|
|
|
76
|
90
|
|
|
90
|
0
|
109
|
my $sub = pop; |
|
77
|
90
|
|
|
|
|
923
|
*$_ = $sub for @_ |
|
78
|
|
|
|
|
|
|
} |
|
79
|
|
|
|
|
|
|
|
|
80
|
2
|
|
|
|
|
10
|
for my $res (keys %res) { |
|
81
|
|
|
|
|
|
|
dynsub $res, "get_$res", sub { |
|
82
|
0
|
|
|
0
|
|
0
|
local *__ANON__ = $res; |
|
83
|
0
|
|
|
|
|
0
|
_tores shift->_get("$res{$res}/$_[0]")->{$res} |
|
84
|
18
|
|
|
|
|
89
|
}; |
|
85
|
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
dynsub $res.'s', "list_$res".'s', sub { |
|
87
|
0
|
|
|
0
|
|
0
|
local *__ANON__ = $res.'s'; |
|
88
|
0
|
|
|
|
|
0
|
my @ret = _tores @{shift->_get($res{$res})->{$res.'s'}}; |
|
|
0
|
|
|
|
|
0
|
|
|
89
|
0
|
0
|
|
|
|
0
|
wantarray ? @ret : $ret[0] |
|
90
|
18
|
|
|
|
|
82
|
}; |
|
91
|
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
dynsub "delete_$res", sub { |
|
93
|
0
|
|
|
0
|
|
0
|
local *__ANON__ = "delete_$res"; |
|
94
|
0
|
|
|
|
|
0
|
shift->_delete("$res{$res}/$_[0]") |
|
95
|
18
|
|
|
|
|
104
|
}; |
|
96
|
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
dynsub "create_$res", sub { |
|
98
|
0
|
|
|
0
|
|
0
|
local *__ANON__ = "create_$res"; |
|
99
|
0
|
|
|
|
|
0
|
my $self = shift; |
|
100
|
0
|
|
|
|
|
0
|
my $content = $_[0]; |
|
101
|
0
|
0
|
0
|
|
|
0
|
if (blessed $content || ref $content ne 'HASH') { |
|
102
|
0
|
0
|
|
|
|
0
|
croak "create_$res does not understand positional parameters, pass a hashref instead\n" unless $create_parms{$res}; |
|
103
|
0
|
|
|
|
|
0
|
my @parms = @{$create_parms{$res}}; |
|
|
0
|
|
|
|
|
0
|
|
|
104
|
|
|
|
|
|
|
$content = { map { |
|
105
|
0
|
0
|
|
|
|
0
|
$parms[$_] => (blessed $_[$_] ? $_[$_]->id : $_[$_]) } 0 .. $#_ }; |
|
|
0
|
|
|
|
|
0
|
|
|
106
|
|
|
|
|
|
|
} |
|
107
|
0
|
|
|
|
|
0
|
_tores $self->_post($res{$res}, { content => encode_json $content })->{$res} |
|
108
|
18
|
|
|
|
|
74
|
}; |
|
109
|
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
dynsub "update_$res", sub { |
|
111
|
0
|
|
|
0
|
|
0
|
local *__ANON__ = "update_$res"; |
|
112
|
0
|
0
|
|
|
|
0
|
my $data = blessed $_[1] ? {%{$_[1]}} : $_[1]; |
|
|
0
|
|
|
|
|
0
|
|
|
113
|
0
|
|
|
|
|
0
|
shift->_put("$res{$res}/".$data->{id}, { content => encode_json $data }) |
|
114
|
18
|
|
|
|
|
112
|
}; |
|
115
|
|
|
|
|
|
|
} |
|
116
|
|
|
|
|
|
|
} |
|
117
|
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
sub security_group_rule { |
|
119
|
|
|
|
|
|
|
_tores shift->_get(_api "/security_groups/$_[0]/rules/$_[1]")->{rule} |
|
120
|
0
|
|
|
0
|
1
|
|
} |
|
121
|
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
sub security_group_rules { |
|
123
|
0
|
|
|
0
|
1
|
|
_tores @{shift->_get(_api "/security_groups/$_[0]/rules")->{rules}} |
|
|
0
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
} |
|
125
|
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
BEGIN { |
|
127
|
2
|
|
|
2
|
|
5
|
*get_security_group_rule = \&security_group_rule; |
|
128
|
2
|
|
|
|
|
652
|
*list_security_group_rule = \&security_group_rules; |
|
129
|
|
|
|
|
|
|
} |
|
130
|
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
sub delete_security_group_rule { |
|
132
|
0
|
|
|
0
|
1
|
|
shift->_delete(_api "/security_groups/$_[0]/rules/$_[1]") |
|
133
|
|
|
|
|
|
|
} |
|
134
|
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
sub create_security_group_rule { |
|
136
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
|
137
|
0
|
|
|
|
|
|
my $grp = shift; |
|
138
|
0
|
|
|
|
|
|
my $content = $_[0]; |
|
139
|
0
|
0
|
|
|
|
|
unless (ref $content eq 'HASH') { |
|
140
|
0
|
|
|
|
|
|
my @parms = qw/organization action direction ip_range protocol dest_port_from/; |
|
141
|
0
|
|
|
|
|
|
$content = { map { $parms[$_] => $_[$_] } 0 .. $#_ }; |
|
|
0
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
} |
|
143
|
0
|
|
|
|
|
|
$self->_post(_api "/security_groups/$grp/rules", { content => encode_json $content }) |
|
144
|
|
|
|
|
|
|
} |
|
145
|
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
sub update_security_group_rule { |
|
147
|
0
|
0
|
|
0
|
1
|
|
my $data = blessed $_[2] ? {%{$_[2]}} : $_[2]; |
|
|
0
|
|
|
|
|
|
|
|
148
|
0
|
|
|
|
|
|
shift->_put (_api "/security_groups/$_[0]/rules/".$data->{id}, { content => encode_json $data }) |
|
149
|
|
|
|
|
|
|
} |
|
150
|
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
sub server_actions { |
|
152
|
0
|
|
|
0
|
1
|
|
@{shift->_get(_api "/servers/$_[0]/action")->{actions}} |
|
|
0
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
} |
|
154
|
|
|
|
|
|
|
|
|
155
|
2
|
|
|
2
|
|
378
|
BEGIN { *list_server_actions = \&server_actions } |
|
156
|
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
sub perform_server_action { |
|
158
|
0
|
|
|
0
|
1
|
|
my $content = encode_json { action => $_[2] }; |
|
159
|
0
|
|
|
|
|
|
_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
|
0
|
|
|
0
|
1
|
|
} |
|
165
|
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
sub server_metadata { |
|
167
|
0
|
|
|
0
|
1
|
|
_tores $dummy->_get('http://169.254.42.42/conf?format=json') |
|
168
|
|
|
|
|
|
|
} |
|
169
|
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
package # hide from PAUSE |
|
171
|
|
|
|
|
|
|
WebService::Scaleway::Resource; |
|
172
|
|
|
|
|
|
|
|
|
173
|
2
|
|
|
2
|
|
13
|
use overload '""' => sub { shift->id }; |
|
|
2
|
|
|
0
|
|
5
|
|
|
|
2
|
|
|
|
|
23
|
|
|
|
0
|
|
|
|
|
0
|
|
|
174
|
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
our $AUTOLOAD; |
|
176
|
|
|
|
|
|
|
sub AUTOLOAD { |
|
177
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
|
178
|
0
|
|
|
|
|
|
my ($attr) = $AUTOLOAD =~ m/::([^:]*)$/s; |
|
179
|
0
|
0
|
|
|
|
|
die "No such attribute: $attr" unless exists $self->{$attr}; |
|
180
|
0
|
|
|
|
|
|
$self->{$attr} |
|
181
|
|
|
|
|
|
|
} |
|
182
|
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
sub can { |
|
184
|
0
|
|
|
0
|
|
|
my ($self, $sub) = @_; |
|
185
|
0
|
|
|
0
|
|
|
exists $self->{$sub} ? sub { shift->{$sub} } : undef |
|
186
|
0
|
0
|
|
|
|
|
} |
|
187
|
|
|
|
|
|
|
|
|
188
|
|
|
|
0
|
|
|
sub DESTROY {} # Don't call AUTOLOAD on destruction |
|
189
|
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
1; |
|
191
|
|
|
|
|
|
|
__END__ |