| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Net::OpenStack::Client::Identity::v3; |
|
2
|
|
|
|
|
|
|
$Net::OpenStack::Client::Identity::v3::VERSION = '0.1.4'; |
|
3
|
2
|
|
|
2
|
|
7999
|
use strict; |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
51
|
|
|
4
|
2
|
|
|
2
|
|
8
|
use warnings; |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
52
|
|
|
5
|
|
|
|
|
|
|
|
|
6
|
2
|
|
|
2
|
|
777
|
use Set::Scalar; |
|
|
2
|
|
|
|
|
18166
|
|
|
|
2
|
|
|
|
|
79
|
|
|
7
|
2
|
|
|
2
|
|
13
|
use Readonly; |
|
|
2
|
|
|
|
|
3
|
|
|
|
2
|
|
|
|
|
68
|
|
|
8
|
|
|
|
|
|
|
|
|
9
|
2
|
|
|
2
|
|
11
|
use Net::OpenStack::Client::API::Convert qw(convert); |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
63
|
|
|
10
|
2
|
|
|
2
|
|
380
|
use Net::OpenStack::Client::Identity::Tagstore; |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
51
|
|
|
11
|
2
|
|
|
2
|
|
12
|
use Net::OpenStack::Client::Request qw(mkrequest); |
|
|
2
|
|
|
|
|
3
|
|
|
|
2
|
|
|
|
|
74
|
|
|
12
|
|
|
|
|
|
|
|
|
13
|
2
|
|
|
2
|
|
429
|
use MIME::Base64 qw(encode_base64url decode_base64url); |
|
|
2
|
|
|
|
|
533
|
|
|
|
2
|
|
|
|
|
6016
|
|
|
14
|
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
Readonly my $IDREG => qr{[0-9a-z]{33}}; |
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
# This list is ordered: |
|
18
|
|
|
|
|
|
|
# Configuration of n-th item does not require |
|
19
|
|
|
|
|
|
|
# configuration of any items after that, but |
|
20
|
|
|
|
|
|
|
# might require configuration of previous ones |
|
21
|
|
|
|
|
|
|
Readonly our @SUPPORTED_OPERATIONS => qw( |
|
22
|
|
|
|
|
|
|
region |
|
23
|
|
|
|
|
|
|
domain |
|
24
|
|
|
|
|
|
|
project |
|
25
|
|
|
|
|
|
|
user |
|
26
|
|
|
|
|
|
|
group |
|
27
|
|
|
|
|
|
|
role |
|
28
|
|
|
|
|
|
|
rolemap |
|
29
|
|
|
|
|
|
|
service |
|
30
|
|
|
|
|
|
|
endpoint |
|
31
|
|
|
|
|
|
|
); |
|
32
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
Readonly my %PARENT_ATTR => { |
|
34
|
|
|
|
|
|
|
region => 'parent_region_id', |
|
35
|
|
|
|
|
|
|
project => 'parent_id', |
|
36
|
|
|
|
|
|
|
}; |
|
37
|
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
# tagstore cache |
|
39
|
|
|
|
|
|
|
# key is project id; value is instance |
|
40
|
|
|
|
|
|
|
my $_tagstores = {}; |
|
41
|
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
=head1 Functions |
|
43
|
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
=over |
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
=item sort_parent |
|
47
|
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
Sort according to parent attribute. |
|
49
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
=cut |
|
51
|
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
# Use toposort? |
|
53
|
|
|
|
|
|
|
# see https://rosettacode.org/wiki/Topological_sort#Perl |
|
54
|
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
sub sort_parent |
|
56
|
|
|
|
|
|
|
{ |
|
57
|
|
|
|
|
|
|
# We assume that an empty string or number 0 is not a valid/used region name |
|
58
|
|
|
|
|
|
|
# force strings, so we can do eq tests |
|
59
|
14
|
|
|
14
|
1
|
17
|
my $ra = $a->{name}; |
|
60
|
14
|
|
|
|
|
17
|
my $rb = $b->{name}; |
|
61
|
14
|
|
100
|
|
|
28
|
my $pra = $a->{parent} || ''; |
|
62
|
14
|
|
100
|
|
|
29
|
my $prb = $b->{parent} || ''; |
|
63
|
|
|
|
|
|
|
|
|
64
|
14
|
|
|
|
|
18
|
my $res; |
|
65
|
14
|
100
|
100
|
|
|
51
|
if ($pra eq $rb) { |
|
|
|
100
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
# b is parent of a: order b a |
|
67
|
2
|
|
|
|
|
4
|
$res = 1; |
|
68
|
|
|
|
|
|
|
} elsif ($prb eq $ra) { |
|
69
|
|
|
|
|
|
|
# a is parent of b: order a b |
|
70
|
1
|
|
|
|
|
1
|
$res = -1; |
|
71
|
|
|
|
|
|
|
} elsif ($pra && !$prb) { |
|
72
|
|
|
|
|
|
|
# a has parent, b does not: order b a |
|
73
|
1
|
|
|
|
|
1
|
$res = 1; |
|
74
|
|
|
|
|
|
|
} elsif ($prb && !$pra) { |
|
75
|
|
|
|
|
|
|
# b has parent, a does not: order a b |
|
76
|
3
|
|
|
|
|
5
|
$res = -1; |
|
77
|
|
|
|
|
|
|
} else { |
|
78
|
|
|
|
|
|
|
# does not matter, use alphabetical sort |
|
79
|
7
|
|
|
|
|
9
|
$res = $ra cmp $rb; |
|
80
|
|
|
|
|
|
|
} |
|
81
|
|
|
|
|
|
|
|
|
82
|
14
|
|
|
|
|
23
|
return $res; |
|
83
|
|
|
|
|
|
|
} |
|
84
|
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
=item sort_parents |
|
86
|
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
Sort arrayref of C with data from C using parent C. |
|
88
|
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
=cut |
|
90
|
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
sub sort_parents |
|
92
|
|
|
|
|
|
|
{ |
|
93
|
2
|
|
|
2
|
1
|
780
|
my ($names, $items, $attr) = @_; |
|
94
|
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
# Assume the id is equal to the name of the region |
|
96
|
2
|
|
|
|
|
6
|
my @snames = sort sort_parent (map {{name => $_, parent => $items->{$_}->{$attr}}} @$names); |
|
|
9
|
|
|
|
|
27
|
|
|
97
|
2
|
|
|
|
|
4
|
return map {$_->{name}} @snames; |
|
|
9
|
|
|
|
|
21
|
|
|
98
|
|
|
|
|
|
|
} |
|
99
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
=item rest |
|
101
|
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
Convenience wrapper for direct REST calls |
|
103
|
|
|
|
|
|
|
for C, C and options C. |
|
104
|
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
=cut |
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
sub rest |
|
108
|
|
|
|
|
|
|
{ |
|
109
|
25
|
|
|
25
|
1
|
72
|
my ($self, $method, $operation, %ropts) = @_; |
|
110
|
25
|
|
|
|
|
74
|
my $defropts = { |
|
111
|
|
|
|
|
|
|
method => $method, |
|
112
|
|
|
|
|
|
|
version => 'v3', |
|
113
|
|
|
|
|
|
|
service => 'identity', |
|
114
|
|
|
|
|
|
|
}; |
|
115
|
|
|
|
|
|
|
|
|
116
|
25
|
|
|
|
|
104
|
%ropts = (%$defropts, %ropts); |
|
117
|
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
# generate raw data |
|
119
|
25
|
100
|
|
|
|
81
|
$ropts{raw} = {$operation => delete $ropts{data}} if ($ropts{data}); |
|
120
|
|
|
|
|
|
|
|
|
121
|
25
|
|
100
|
|
|
100
|
my $endpoint = "${operation}s/" . (delete $ropts{what} || '') . "?name=name"; |
|
122
|
|
|
|
|
|
|
|
|
123
|
25
|
|
|
|
|
95
|
return $self->rest(mkrequest($endpoint, $method, %ropts)); |
|
124
|
|
|
|
|
|
|
}; |
|
125
|
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
=item get_id |
|
127
|
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
Return the ID of an C. |
|
129
|
|
|
|
|
|
|
If the name is an ID, return the ID without a lookup. |
|
130
|
|
|
|
|
|
|
If the operation is 'region', return the name. |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
Options |
|
133
|
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
=over |
|
135
|
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
=item error: report an error when no id is found |
|
137
|
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
=item msg: use the value as (part of) the reported message |
|
139
|
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
=back |
|
141
|
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=cut |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
sub get_id |
|
145
|
|
|
|
|
|
|
{ |
|
146
|
16
|
|
|
16
|
1
|
74
|
my ($self, $operation, $name, %opts) = @_; |
|
147
|
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
# region has no id (or no name, whatever you like) |
|
149
|
16
|
100
|
66
|
|
|
49
|
return $name if ($name =~ m/$IDREG/ || $operation eq 'region'); |
|
150
|
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
# GET the list for name |
|
152
|
12
|
|
|
|
|
200
|
my $resp = $self->api_identity_rest('GET', $operation, result => "/${operation}s", params => {name => $name}); |
|
153
|
|
|
|
|
|
|
|
|
154
|
12
|
|
|
|
|
41
|
my $msg = "found for $operation with name $name"; |
|
155
|
12
|
100
|
|
|
|
36
|
$msg .= " $opts{msg}" if $opts{msg}; |
|
156
|
|
|
|
|
|
|
|
|
157
|
12
|
|
|
|
|
14
|
my $id; |
|
158
|
12
|
50
|
|
|
|
28
|
if ($resp) { |
|
159
|
12
|
50
|
|
|
|
17
|
my @ids = (map {$_->{id}} @{$resp->result || []}); |
|
|
12
|
|
|
|
|
31
|
|
|
|
12
|
|
|
|
|
26
|
|
|
160
|
12
|
50
|
|
|
|
36
|
if (scalar @ids > 1) { |
|
|
|
50
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
# what? do not return anything |
|
162
|
0
|
|
|
|
|
0
|
$self->error("More than one ID $msg: @ids"); |
|
163
|
|
|
|
|
|
|
} elsif (@ids) { |
|
164
|
12
|
|
|
|
|
17
|
$id = $ids[0]; |
|
165
|
12
|
|
|
|
|
44
|
$self->verbose("ID $id $msg"); |
|
166
|
|
|
|
|
|
|
} else { |
|
167
|
0
|
0
|
|
|
|
0
|
my $method = $opts{error} ? 'error' : 'verbose'; |
|
168
|
0
|
|
|
|
|
0
|
$self->$method("No ID $msg"); |
|
169
|
|
|
|
|
|
|
} |
|
170
|
|
|
|
|
|
|
} else { |
|
171
|
0
|
|
|
|
|
0
|
$self->error("get_id invalid request $msg: $resp->{error}"); |
|
172
|
|
|
|
|
|
|
}; |
|
173
|
|
|
|
|
|
|
|
|
174
|
12
|
|
|
|
|
3178
|
return $id; |
|
175
|
|
|
|
|
|
|
} |
|
176
|
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
# Function to retrun the name attribute based on the the operation |
|
178
|
|
|
|
|
|
|
sub _name_attribute |
|
179
|
|
|
|
|
|
|
{ |
|
180
|
11
|
|
|
11
|
|
18
|
my ($operation) = @_; |
|
181
|
11
|
100
|
|
|
|
24
|
return $operation eq 'region' ? 'id' : 'name'; |
|
182
|
|
|
|
|
|
|
} |
|
183
|
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
# Function to return the name based on the operation and data |
|
185
|
|
|
|
|
|
|
sub _make_name |
|
186
|
|
|
|
|
|
|
{ |
|
187
|
8
|
|
|
8
|
|
13
|
my ($operation, $data) = @_; |
|
188
|
8
|
100
|
|
|
|
16
|
if ($operation eq 'endpoint') { |
|
189
|
|
|
|
|
|
|
# for endpoint, we construct an internal unique name based on |
|
190
|
|
|
|
|
|
|
# interface and url, seperated by a underscore |
|
191
|
3
|
|
|
|
|
10
|
return "$data->{interface}_$data->{url}"; |
|
192
|
|
|
|
|
|
|
} else { |
|
193
|
5
|
|
|
|
|
10
|
my $attr = _name_attribute($operation); |
|
194
|
5
|
|
|
|
|
17
|
return $data->{$attr}; |
|
195
|
|
|
|
|
|
|
} |
|
196
|
|
|
|
|
|
|
} |
|
197
|
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
=item tagstore_init |
|
199
|
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
Function to initialise tagstore or return cached version based on tagstore project name. |
|
201
|
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
=cut |
|
203
|
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
sub tagstore_init |
|
205
|
|
|
|
|
|
|
{ |
|
206
|
3
|
|
|
3
|
1
|
7
|
my ($client, $tagstore_proj) = @_; |
|
207
|
|
|
|
|
|
|
|
|
208
|
3
|
100
|
|
|
|
9
|
if (!$_tagstores->{$tagstore_proj}) { |
|
209
|
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
# Does the project exist? |
|
211
|
1
|
|
|
|
|
10
|
my $resp = $client->api_identity_projects(name => $tagstore_proj); |
|
212
|
1
|
50
|
|
|
|
3
|
if ($resp) { |
|
213
|
1
|
|
|
|
|
2
|
my @proj = @{$resp->result}; |
|
|
1
|
|
|
|
|
3
|
|
|
214
|
1
|
50
|
|
|
|
4
|
if (scalar @proj > 1) { |
|
|
|
50
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
$client->error("More than one tagstore project $tagstore_proj found: ids ", |
|
216
|
0
|
|
|
|
|
0
|
join(",", map {$_->{id}} @proj), ". Unsupported for now"); |
|
|
0
|
|
|
|
|
0
|
|
|
217
|
0
|
|
|
|
|
0
|
return; |
|
218
|
|
|
|
|
|
|
} elsif (scalar @proj == 1) { |
|
219
|
1
|
|
|
|
|
6
|
$client->verbose("Found one tagstore project $tagstore_proj id ", $proj[0]->{id}); |
|
220
|
|
|
|
|
|
|
} else { |
|
221
|
0
|
|
|
|
|
0
|
$resp = $client->api_identity_add_project(name => $tagstore_proj, |
|
222
|
|
|
|
|
|
|
description => "Main tagstore project $tagstore_proj"); |
|
223
|
0
|
0
|
|
|
|
0
|
if ($resp) { |
|
224
|
0
|
|
|
|
|
0
|
$client->verbose("Created main tagstore project $tagstore_proj id ", $resp->result->{id}); |
|
225
|
|
|
|
|
|
|
} else { |
|
226
|
0
|
|
|
|
|
0
|
$client->error("Failed to add main tagstore project $tagstore_proj: $resp->{error}"); |
|
227
|
0
|
|
|
|
|
0
|
return; |
|
228
|
|
|
|
|
|
|
} |
|
229
|
|
|
|
|
|
|
} |
|
230
|
|
|
|
|
|
|
} else { |
|
231
|
0
|
|
|
|
|
0
|
$client->error("Failed to list possible tagstore project $tagstore_proj: $resp->{error}"); |
|
232
|
0
|
|
|
|
|
0
|
return; |
|
233
|
|
|
|
|
|
|
} |
|
234
|
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
# Get instance |
|
236
|
1
|
|
|
|
|
265
|
my $tgst = Net::OpenStack::Client::Identity::Tagstore->new( |
|
237
|
|
|
|
|
|
|
$client, |
|
238
|
|
|
|
|
|
|
$tagstore_proj, |
|
239
|
|
|
|
|
|
|
); |
|
240
|
|
|
|
|
|
|
|
|
241
|
1
|
50
|
|
|
|
4
|
if ($tgst) { |
|
242
|
1
|
|
|
|
|
6
|
$_tagstores->{$tagstore_proj} = $tgst; |
|
243
|
|
|
|
|
|
|
} else { |
|
244
|
0
|
|
|
|
|
0
|
$client->error("sync: failed to create new tagstore for project $tagstore_proj"); |
|
245
|
0
|
|
|
|
|
0
|
return; |
|
246
|
|
|
|
|
|
|
} |
|
247
|
|
|
|
|
|
|
} |
|
248
|
|
|
|
|
|
|
|
|
249
|
3
|
|
|
|
|
7
|
return $_tagstores->{$tagstore_proj}; |
|
250
|
|
|
|
|
|
|
} |
|
251
|
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
=item tagstore_postprocess |
|
253
|
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
Function to postprocess sync operations when a tagstore is used. |
|
255
|
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
=cut |
|
257
|
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
sub tagstore_postprocess |
|
259
|
|
|
|
|
|
|
{ |
|
260
|
7
|
|
|
7
|
1
|
17
|
my ($tagstore, $phase, $operation, $name, $result) = @_; |
|
261
|
|
|
|
|
|
|
|
|
262
|
7
|
|
|
|
|
23
|
my $msg = "sync postprocess $operation $name stopped after failure to $phase"; |
|
263
|
7
|
50
|
|
|
|
18
|
if (exists($result->{id})) { |
|
264
|
7
|
|
|
|
|
14
|
my $id = $result->{id}; |
|
265
|
7
|
|
|
|
|
10
|
my $ok = 1; |
|
266
|
|
|
|
|
|
|
|
|
267
|
7
|
100
|
100
|
|
|
28
|
if ($phase eq 'create' || $phase eq 'delete') { |
|
268
|
6
|
100
|
|
|
|
14
|
my $method = $phase eq 'create' ? 'add' : $phase; |
|
269
|
6
|
|
|
|
|
31
|
$ok = $tagstore->$method("ID_${operation}_${id}"); |
|
270
|
|
|
|
|
|
|
} else { |
|
271
|
1
|
|
|
|
|
5
|
$tagstore->verbose("sync: nothing to do for tagstore postprocessing during $phase for $name id $id"); |
|
272
|
|
|
|
|
|
|
} |
|
273
|
|
|
|
|
|
|
|
|
274
|
7
|
50
|
|
|
|
281
|
if ($ok) { |
|
275
|
7
|
|
|
|
|
25
|
return 1; |
|
276
|
|
|
|
|
|
|
} else { |
|
277
|
0
|
|
|
|
|
0
|
$tagstore->error("$msg tag $id to tagstore. See previous error where to add the tag to continue"); |
|
278
|
0
|
|
|
|
|
0
|
return; |
|
279
|
|
|
|
|
|
|
} |
|
280
|
|
|
|
|
|
|
} else { |
|
281
|
0
|
|
|
|
|
0
|
$tagstore->error("$msg no id in response"); |
|
282
|
0
|
|
|
|
|
0
|
return; |
|
283
|
|
|
|
|
|
|
} |
|
284
|
|
|
|
|
|
|
} |
|
285
|
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
=pod |
|
287
|
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
=back |
|
289
|
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
=head1 Methods |
|
291
|
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
=over |
|
293
|
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
=item sync |
|
295
|
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
For an C (like C, C, C, ...), |
|
297
|
|
|
|
|
|
|
given an hashref of C (key is the name), |
|
298
|
|
|
|
|
|
|
compare it with all existing items: |
|
299
|
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
=over |
|
301
|
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
=item Non-existing ones are added/created |
|
303
|
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
=item Existing ones are possibly updated |
|
305
|
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
=item Existing ones that are not requested are disbaled |
|
307
|
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
=back |
|
309
|
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
Returns a hasref with responses for the created items. The keys are |
|
311
|
|
|
|
|
|
|
C, C and C and the values an arrayref of responses. |
|
312
|
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
For C operations, as they have no name, use the C<<_>> |
|
314
|
|
|
|
|
|
|
as the name for the C hashref. |
|
315
|
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
Following options are supported: |
|
317
|
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
=over |
|
319
|
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
=item filter: a function to filter the existing items. |
|
321
|
|
|
|
|
|
|
Return a true value to keep the existing item (false will ignore it). |
|
322
|
|
|
|
|
|
|
By default, all existing items are considered. |
|
323
|
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
=item delete: when the delete option is true, existing items that are |
|
325
|
|
|
|
|
|
|
not in the C hashref, will be deleted (instead of disabled). |
|
326
|
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
=item keep: when the keep option is true, existing items that are |
|
328
|
|
|
|
|
|
|
not in the C hashref are ignored. |
|
329
|
|
|
|
|
|
|
This precedes any value of C option. |
|
330
|
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
=item tagstore: use project tagstore to track synced ids. |
|
332
|
|
|
|
|
|
|
If no filter is set, the tagstore is used to filter known ids |
|
333
|
|
|
|
|
|
|
as existing tags in the tagstore. |
|
334
|
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
=back |
|
336
|
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
=cut |
|
338
|
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
sub sync |
|
340
|
|
|
|
|
|
|
{ |
|
341
|
3
|
|
|
3
|
1
|
11
|
my ($self, $operation, $items, %opts) = @_; |
|
342
|
|
|
|
|
|
|
|
|
343
|
3
|
50
|
|
|
|
11
|
if (! grep {$_ eq $operation} @SUPPORTED_OPERATIONS) { |
|
|
27
|
|
|
|
|
138
|
|
|
344
|
0
|
|
|
|
|
0
|
$self->error("Unsupported operation $operation"); |
|
345
|
0
|
|
|
|
|
0
|
return; |
|
346
|
|
|
|
|
|
|
} |
|
347
|
|
|
|
|
|
|
|
|
348
|
3
|
|
|
|
|
9
|
my $tagstore; |
|
349
|
3
|
100
|
|
|
|
13
|
$tagstore = tagstore_init($self, $opts{tagstore}) if $opts{tagstore}; |
|
350
|
|
|
|
|
|
|
|
|
351
|
3
|
|
|
|
|
5
|
my $filter; |
|
352
|
3
|
100
|
|
|
|
10
|
if ($opts{filter}) { |
|
|
|
50
|
|
|
|
|
|
|
353
|
1
|
|
|
|
|
2
|
$filter = $opts{filter}; |
|
354
|
1
|
50
|
|
|
|
3
|
if (ref($filter) ne 'CODE') { |
|
355
|
0
|
|
|
|
|
0
|
$self->error("sync filter is not CODE"); |
|
356
|
0
|
|
|
|
|
0
|
return; |
|
357
|
|
|
|
|
|
|
} |
|
358
|
|
|
|
|
|
|
} elsif ($tagstore) { |
|
359
|
2
|
|
|
6
|
|
10
|
$filter = sub {return $tagstore->get("ID_${operation}_".$_[0]->{id})}; |
|
|
6
|
|
|
|
|
27
|
|
|
360
|
|
|
|
|
|
|
} else { |
|
361
|
0
|
|
|
0
|
|
0
|
$filter = sub {return 1}; |
|
|
0
|
|
|
|
|
0
|
|
|
362
|
|
|
|
|
|
|
}; |
|
363
|
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
# GET the list |
|
365
|
3
|
|
|
|
|
20
|
my $resp_list = $self->api_identity_rest('GET', $operation, result => "/${operation}s"); |
|
366
|
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
my $found = { |
|
368
|
8
|
|
|
|
|
20
|
map {_make_name($operation, $_) => $_} |
|
369
|
11
|
|
|
|
|
35
|
grep {$filter->($_)} |
|
370
|
3
|
50
|
|
|
|
9
|
@{$resp_list->result || []} |
|
|
3
|
|
|
|
|
7
|
|
|
371
|
|
|
|
|
|
|
}; |
|
372
|
|
|
|
|
|
|
|
|
373
|
3
|
|
|
|
|
27
|
my $existing = Set::Scalar->new(keys %$found); |
|
374
|
3
|
|
|
|
|
364
|
my $wanted = Set::Scalar->new(keys %$items); |
|
375
|
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
# Add default enabled=1 to all wanted operation |
|
377
|
3
|
|
|
|
|
178
|
foreach my $want (@$wanted) { |
|
378
|
9
|
50
|
|
|
|
120
|
$items->{$want}->{enabled} = convert(1, 'boolean') if ! exists($items->{$want}->{enabled}); |
|
379
|
|
|
|
|
|
|
}; |
|
380
|
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
# compare |
|
382
|
|
|
|
|
|
|
|
|
383
|
3
|
|
|
|
|
14
|
my @tocreate = sort @{$wanted - $existing}; |
|
|
3
|
|
|
|
|
19
|
|
|
384
|
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
# regions and projects can have parent relations, so they need to be sorted accordingly |
|
386
|
|
|
|
|
|
|
# we only expect the order to be important with creation, not for updates or deletes |
|
387
|
|
|
|
|
|
|
# the parent attr might also be the names, not the actual ids |
|
388
|
|
|
|
|
|
|
# e.g. to support ordering not yet created parent |
|
389
|
3
|
|
|
|
|
750
|
my $parentattr = $PARENT_ATTR{$operation}; |
|
390
|
3
|
100
|
|
|
|
139
|
@tocreate = sort_parents(\@tocreate, $items, $parentattr) if $parentattr; |
|
391
|
|
|
|
|
|
|
|
|
392
|
3
|
|
|
|
|
11
|
my $res = { |
|
393
|
|
|
|
|
|
|
create => [], |
|
394
|
|
|
|
|
|
|
update => [], |
|
395
|
|
|
|
|
|
|
delete => [], |
|
396
|
|
|
|
|
|
|
}; |
|
397
|
|
|
|
|
|
|
|
|
398
|
3
|
|
|
|
|
5
|
my $postprocess; |
|
399
|
3
|
100
|
|
7
|
|
11
|
$postprocess = sub { return tagstore_postprocess($tagstore, @_) } if ($tagstore); |
|
|
7
|
|
|
|
|
21
|
|
|
400
|
|
|
|
|
|
|
|
|
401
|
3
|
50
|
|
|
|
38
|
my $created = $self->api_identity_create($operation, \@tocreate, $items, $res, $postprocess) or return; |
|
402
|
|
|
|
|
|
|
|
|
403
|
3
|
|
|
|
|
5
|
my @checkupdate = sort @{$wanted * $existing}; |
|
|
3
|
|
|
|
|
15
|
|
|
404
|
3
|
50
|
|
|
|
870
|
$self->api_identity_update($operation, \@checkupdate, $found, $items, $res, $postprocess) or return; |
|
405
|
|
|
|
|
|
|
# no tagstore operations? |
|
406
|
|
|
|
|
|
|
|
|
407
|
3
|
|
|
|
|
7
|
my @toremove = sort @{$existing - $wanted}; |
|
|
3
|
|
|
|
|
12
|
|
|
408
|
3
|
50
|
|
|
|
664
|
$self->api_identity_delete($operation, \@toremove, $found, \%opts, $res, $postprocess) or return; |
|
409
|
|
|
|
|
|
|
|
|
410
|
3
|
|
|
|
|
19
|
return $res; |
|
411
|
|
|
|
|
|
|
} |
|
412
|
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
=item get_item |
|
414
|
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
Retrieve and augment an item with C from hashref C. |
|
416
|
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
Modification to the data |
|
418
|
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
=over |
|
420
|
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
=item name is inserted (unless this is an endpoint) |
|
422
|
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
=item any named ids (either from (other) operation(s) or parenting) are resolved |
|
424
|
|
|
|
|
|
|
to their actual id. |
|
425
|
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
=back |
|
427
|
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
=cut |
|
429
|
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
sub get_item |
|
431
|
|
|
|
|
|
|
{ |
|
432
|
9
|
|
|
9
|
1
|
22
|
my ($self, $operation, $name, $items) = @_; |
|
433
|
|
|
|
|
|
|
|
|
434
|
9
|
|
|
|
|
15
|
my $new = $items->{$name}; |
|
435
|
|
|
|
|
|
|
|
|
436
|
9
|
100
|
|
|
|
19
|
if ($operation ne 'endpoint') { |
|
437
|
6
|
|
|
|
|
14
|
my $nameattr = _name_attribute($operation); |
|
438
|
|
|
|
|
|
|
# add name |
|
439
|
6
|
|
|
|
|
14
|
$new->{$nameattr} = $name; |
|
440
|
|
|
|
|
|
|
} |
|
441
|
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
# resolve ids |
|
443
|
9
|
|
|
|
|
28
|
my %toresolve = (map {$_."_id" => $_} @SUPPORTED_OPERATIONS); |
|
|
81
|
|
|
|
|
497
|
|
|
444
|
|
|
|
|
|
|
# resolve parent ids |
|
445
|
9
|
100
|
|
|
|
47
|
$toresolve{$PARENT_ATTR{$operation}} = $operation if $PARENT_ATTR{$operation}; |
|
446
|
|
|
|
|
|
|
|
|
447
|
9
|
|
|
|
|
119
|
foreach my $attr (sort keys %toresolve) { |
|
448
|
|
|
|
|
|
|
# no autovivification |
|
449
|
84
|
100
|
|
|
|
137
|
next if ! exists($new->{$attr}); |
|
450
|
|
|
|
|
|
|
|
|
451
|
5
|
|
|
|
|
39
|
my $resolved = $self->api_identity_get_id($toresolve{$attr}, $new->{$attr}, error => 1); |
|
452
|
5
|
50
|
|
|
|
48
|
if (defined($resolved)) { |
|
453
|
5
|
|
|
|
|
13
|
$new->{$attr} = $resolved; |
|
454
|
|
|
|
|
|
|
} else { |
|
455
|
0
|
|
|
|
|
0
|
$self->error("Failed to resolve id for $operation name $name attr $attr with value $new->{$attr}"); |
|
456
|
0
|
|
|
|
|
0
|
return; |
|
457
|
|
|
|
|
|
|
} |
|
458
|
|
|
|
|
|
|
} |
|
459
|
|
|
|
|
|
|
|
|
460
|
9
|
|
|
|
|
46
|
return $new; |
|
461
|
|
|
|
|
|
|
} |
|
462
|
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
=item _process_response |
|
464
|
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
Helper function for all 3 sync phases |
|
466
|
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
C is updated in place. |
|
468
|
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
Returns 1 on success, undef otherwise (and reports an error). |
|
470
|
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
=cut |
|
472
|
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
sub _process_response |
|
474
|
|
|
|
|
|
|
{ |
|
475
|
10
|
|
|
10
|
|
29
|
my ($client, $phase, $resp, $res, $operation, $name, $postprocess) = @_; |
|
476
|
|
|
|
|
|
|
|
|
477
|
10
|
50
|
|
|
|
21
|
if ($resp) { |
|
478
|
10
|
|
|
|
|
29
|
my $result = $resp->result("/$operation"); |
|
479
|
10
|
|
|
|
|
19
|
push(@{$res->{$phase}}, [$name, $result]); |
|
|
10
|
|
|
|
|
31
|
|
|
480
|
10
|
|
|
|
|
40
|
$client->verbose("sync: ${phase}d $operation $name"); |
|
481
|
10
|
100
|
|
|
|
2532
|
if ($postprocess) { |
|
482
|
7
|
50
|
|
|
|
18
|
$postprocess->($phase, $operation, $name, $result) or return; |
|
483
|
|
|
|
|
|
|
} |
|
484
|
10
|
|
|
|
|
53
|
return 1; |
|
485
|
|
|
|
|
|
|
} else { |
|
486
|
0
|
|
|
|
|
0
|
$client->error("sync: failed to $phase $operation $name: $resp->{error}"); |
|
487
|
0
|
|
|
|
|
0
|
return; |
|
488
|
|
|
|
|
|
|
} |
|
489
|
|
|
|
|
|
|
} |
|
490
|
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
=item create |
|
493
|
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
Create C items in arrayref C from configured C |
|
495
|
|
|
|
|
|
|
(using name attriute C), |
|
496
|
|
|
|
|
|
|
with result hashref C. C is updated in place. |
|
497
|
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
C is a anonymous function called after a succesful REST call, |
|
499
|
|
|
|
|
|
|
and is passed following arguments: |
|
500
|
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
=over |
|
502
|
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
=item phase: one of C, C or C, depending on what pahse of the sync |
|
504
|
|
|
|
|
|
|
the REST call is made. |
|
505
|
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
=item operation: type of operation |
|
507
|
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
=item name: name of the operation |
|
509
|
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
=item result: result of the REST call |
|
511
|
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
=back |
|
513
|
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
=cut |
|
515
|
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
sub create |
|
517
|
|
|
|
|
|
|
{ |
|
518
|
3
|
|
|
3
|
1
|
11
|
my ($self, $operation, $tocreate, $items, $res, $postprocess) = @_; |
|
519
|
|
|
|
|
|
|
|
|
520
|
3
|
|
|
|
|
7
|
my @tocreate = @$tocreate; |
|
521
|
|
|
|
|
|
|
|
|
522
|
3
|
50
|
|
|
|
8
|
if (@tocreate) { |
|
523
|
3
|
|
|
|
|
25
|
$self->info("Creating ${operation}s: @tocreate"); |
|
524
|
3
|
|
|
|
|
781
|
foreach my $name (@tocreate) { |
|
525
|
|
|
|
|
|
|
# POST to create |
|
526
|
5
|
50
|
|
|
|
40
|
my $new = $self->api_identity_get_item($operation, $name, $items) or return; |
|
527
|
5
|
|
|
|
|
29
|
my $resp = $self->api_identity_rest('POST', $operation, data => $new); |
|
528
|
5
|
50
|
|
|
|
17
|
_process_response($self, 'create', $resp, $res, $operation, $name, $postprocess) or return; |
|
529
|
|
|
|
|
|
|
} |
|
530
|
|
|
|
|
|
|
} else { |
|
531
|
0
|
|
|
|
|
0
|
$self->verbose("No ${operation}s to create"); |
|
532
|
|
|
|
|
|
|
} |
|
533
|
|
|
|
|
|
|
|
|
534
|
3
|
|
|
|
|
14
|
return 1; |
|
535
|
|
|
|
|
|
|
} |
|
536
|
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
=item update |
|
538
|
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
Update C items in arrayref C from C items |
|
540
|
|
|
|
|
|
|
with configured C, with result hashref C. |
|
541
|
|
|
|
|
|
|
C is updated in place. |
|
542
|
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
=cut |
|
544
|
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
sub update |
|
546
|
|
|
|
|
|
|
{ |
|
547
|
3
|
|
|
3
|
1
|
9
|
my ($self, $operation, $checkupdate, $found, $items, $res, $postprocess) = @_; |
|
548
|
|
|
|
|
|
|
|
|
549
|
3
|
|
|
|
|
7
|
my @checkupdate = @$checkupdate; |
|
550
|
|
|
|
|
|
|
|
|
551
|
3
|
100
|
|
|
|
8
|
if (@checkupdate) { |
|
552
|
2
|
|
|
|
|
12
|
$self->info("Possibly updating existing ${operation}s: @checkupdate"); |
|
553
|
2
|
|
|
|
|
507
|
my @toupdate; |
|
554
|
2
|
|
|
|
|
6
|
foreach my $name (@checkupdate) { |
|
555
|
|
|
|
|
|
|
# anything to update? |
|
556
|
4
|
|
|
|
|
7
|
my $update; |
|
557
|
4
|
50
|
|
|
|
23
|
my $update_data = $self->api_identity_get_item($operation, $name, $items) or return; |
|
558
|
4
|
|
|
|
|
14
|
foreach my $attr (sort keys %$update_data) { |
|
559
|
16
|
|
|
|
|
22
|
my $wa = $update_data ->{$attr}; |
|
560
|
16
|
|
|
|
|
21
|
my $fo = $found->{$name}->{$attr}; |
|
561
|
16
|
100
|
25
|
|
|
69
|
my $action = $attr eq 'enabled' ? ($wa xor $fo): ($wa ne $fo); |
|
562
|
|
|
|
|
|
|
# hmmm, how to keep this JSON safe? |
|
563
|
16
|
100
|
|
|
|
49
|
$update->{$attr} = $wa if $action; |
|
564
|
|
|
|
|
|
|
} |
|
565
|
4
|
100
|
|
|
|
22
|
if (scalar keys %$update) { |
|
566
|
2
|
|
|
|
|
4
|
push(@toupdate, $name); |
|
567
|
2
|
|
|
|
|
11
|
my $resp = $self->api_identity_rest('PATCH', $operation, what => $found->{$name}->{id}, data => $update); |
|
568
|
2
|
50
|
|
|
|
5
|
_process_response($self, 'update', $resp, $res, $operation, $name, $postprocess) or return; |
|
569
|
|
|
|
|
|
|
} |
|
570
|
|
|
|
|
|
|
} |
|
571
|
2
|
50
|
|
|
|
14
|
$self->info(@toupdate ? "Updated existing ${operation}s: @toupdate" : "No existing ${operation}s updated"); |
|
572
|
|
|
|
|
|
|
} else { |
|
573
|
1
|
|
|
|
|
6
|
$self->verbose("No existing ${operation}s to update"); |
|
574
|
|
|
|
|
|
|
} |
|
575
|
|
|
|
|
|
|
|
|
576
|
3
|
|
|
|
|
759
|
return 1; |
|
577
|
|
|
|
|
|
|
} |
|
578
|
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
=item delete |
|
580
|
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
Delete (or disable) C items in arrayref C from C |
|
582
|
|
|
|
|
|
|
existing items, with options C (for C and C) |
|
583
|
|
|
|
|
|
|
and result hashref C. C is updated in place. |
|
584
|
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
When C option is true, nothing will happen. |
|
586
|
|
|
|
|
|
|
When C is true, items will be delete; when items will be disabled. |
|
587
|
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
=cut |
|
589
|
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
sub delete |
|
591
|
|
|
|
|
|
|
{ |
|
592
|
3
|
|
|
3
|
1
|
8
|
my ($self, $operation, $toremove, $found, $opts, $res, $postprocess) = @_; |
|
593
|
|
|
|
|
|
|
|
|
594
|
3
|
|
|
|
|
7
|
my @toremove = @$toremove; |
|
595
|
|
|
|
|
|
|
|
|
596
|
3
|
50
|
|
|
|
14
|
my $dowhat = $opts->{delete} ? 'delet' : 'disabl'; |
|
597
|
|
|
|
|
|
|
|
|
598
|
3
|
50
|
|
|
|
8
|
if (@toremove) { |
|
599
|
3
|
50
|
|
|
|
6
|
if ($opts->{ignore}) { |
|
600
|
0
|
|
|
|
|
0
|
$self->info("Ignoring existing ${operation}s (instead of ${dowhat}ing): @toremove"); |
|
601
|
|
|
|
|
|
|
} else { |
|
602
|
3
|
|
|
|
|
21
|
$self->info(ucfirst($dowhat)."ing existing ${operation}s: @toremove"); |
|
603
|
3
|
|
|
|
|
763
|
foreach my $name (@toremove) { |
|
604
|
4
|
|
|
|
|
8
|
my $resp; |
|
605
|
4
|
50
|
|
|
|
10
|
if ($opts->{delete}) { |
|
606
|
|
|
|
|
|
|
# DELETE to delete |
|
607
|
0
|
|
|
|
|
0
|
$resp = $self->api_identity_rest('DELETE', $operation, what => $found->{$name}->{id}); |
|
608
|
|
|
|
|
|
|
} else { |
|
609
|
|
|
|
|
|
|
# PATCH to disable |
|
610
|
|
|
|
|
|
|
# do not disable if already disabled |
|
611
|
4
|
100
|
|
|
|
12
|
if ($found->{$name}->{enabled}) { |
|
612
|
|
|
|
|
|
|
$resp = $self->api_identity_rest('PATCH', $operation, |
|
613
|
|
|
|
|
|
|
what => $found->{$name}->{id}, |
|
614
|
3
|
|
|
|
|
14
|
data => {enabled => convert(0, 'boolean')}); |
|
615
|
|
|
|
|
|
|
} else { |
|
616
|
|
|
|
|
|
|
$self->verbose("Not disabling already disabled ". |
|
617
|
1
|
|
|
|
|
7
|
"$operation $name (id ".$found->{$name}->{id}.")"); |
|
618
|
|
|
|
|
|
|
} |
|
619
|
|
|
|
|
|
|
} |
|
620
|
|
|
|
|
|
|
|
|
621
|
4
|
100
|
|
|
|
261
|
if (defined($resp)) { |
|
622
|
3
|
50
|
|
|
|
11
|
_process_response($self, 'delete', $resp, $res, $operation, $name, $postprocess) or return; |
|
623
|
|
|
|
|
|
|
} |
|
624
|
|
|
|
|
|
|
} |
|
625
|
|
|
|
|
|
|
} |
|
626
|
|
|
|
|
|
|
} else { |
|
627
|
0
|
|
|
|
|
0
|
$self->verbose("No existing ${operation}s to ${dowhat}e"); |
|
628
|
|
|
|
|
|
|
} |
|
629
|
|
|
|
|
|
|
|
|
630
|
3
|
|
|
|
|
13
|
return 1; |
|
631
|
|
|
|
|
|
|
} |
|
632
|
|
|
|
|
|
|
|
|
633
|
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
=item sync_rolemap |
|
635
|
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
Add missing roles for project/domain and group/user, |
|
637
|
|
|
|
|
|
|
and delete any when tagstore is used. |
|
638
|
|
|
|
|
|
|
|
|
639
|
|
|
|
|
|
|
The roles are defined with a nested hashref, like |
|
640
|
|
|
|
|
|
|
the url is structured (with an arrayref of roles as value). |
|
641
|
|
|
|
|
|
|
E.g. |
|
642
|
|
|
|
|
|
|
$roles = { |
|
643
|
|
|
|
|
|
|
domain => { |
|
644
|
|
|
|
|
|
|
dom1 => { |
|
645
|
|
|
|
|
|
|
user => { |
|
646
|
|
|
|
|
|
|
user1 => [role1 role2], |
|
647
|
|
|
|
|
|
|
... |
|
648
|
|
|
|
|
|
|
}, |
|
649
|
|
|
|
|
|
|
group => { |
|
650
|
|
|
|
|
|
|
... |
|
651
|
|
|
|
|
|
|
}, |
|
652
|
|
|
|
|
|
|
}, |
|
653
|
|
|
|
|
|
|
... |
|
654
|
|
|
|
|
|
|
project => { |
|
655
|
|
|
|
|
|
|
... |
|
656
|
|
|
|
|
|
|
}, |
|
657
|
|
|
|
|
|
|
} |
|
658
|
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
Options |
|
660
|
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
=over |
|
662
|
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
=item tagstore: use project tagstore to track synced roles. |
|
664
|
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
=back |
|
666
|
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
=cut |
|
668
|
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
sub sync_rolemap |
|
671
|
|
|
|
|
|
|
{ |
|
672
|
1
|
|
|
1
|
1
|
4
|
my ($self, $roles, %opts) = @_; |
|
673
|
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
# Get all roles from tagstore (if defined) |
|
675
|
|
|
|
|
|
|
# The role tag is ROLE_url |
|
676
|
|
|
|
|
|
|
# url is |
|
677
|
|
|
|
|
|
|
# projects/{project_id} OR domains/{domain_id} + |
|
678
|
|
|
|
|
|
|
# groups/{group_id} OR users/{user_id} + |
|
679
|
|
|
|
|
|
|
# roles/{role_id} |
|
680
|
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
# Will use url as identifier |
|
682
|
|
|
|
|
|
|
|
|
683
|
1
|
|
|
|
|
2
|
my ($tagstore, @found); |
|
684
|
|
|
|
|
|
|
|
|
685
|
1
|
50
|
|
|
|
4
|
if ($opts{tagstore}) { |
|
686
|
1
|
50
|
|
|
|
4
|
$tagstore = tagstore_init($self, $opts{tagstore}) if $opts{tagstore}; |
|
687
|
|
|
|
|
|
|
# Strip ROLE_, decode/unescape the url |
|
688
|
1
|
|
|
|
|
3
|
@found = map {my $url = $_; $url =~ s/^ROLE_//; decode_base64url($url)} grep {m/^ROLE_/} sort keys %{$tagstore->fetch}; |
|
|
2
|
|
|
|
|
15
|
|
|
|
2
|
|
|
|
|
7
|
|
|
|
2
|
|
|
|
|
6
|
|
|
|
8
|
|
|
|
|
17
|
|
|
|
1
|
|
|
|
|
3
|
|
|
689
|
|
|
|
|
|
|
}; |
|
690
|
1
|
|
|
|
|
11
|
my $existing = Set::Scalar->new(@found); |
|
691
|
|
|
|
|
|
|
|
|
692
|
|
|
|
|
|
|
# create hash: key is url, value is 1 |
|
693
|
1
|
|
|
|
|
66
|
my $items; |
|
694
|
1
|
|
|
|
|
3
|
foreach my $base (qw(project domain)) { |
|
695
|
2
|
50
|
|
|
|
4
|
foreach my $bval (sort keys %{$roles->{$base} || {}}) { |
|
|
2
|
|
|
|
|
10
|
|
|
696
|
2
|
50
|
|
|
|
14
|
my $bid = $self->api_identity_get_id($base, $bval, error => 1, msg => 'for role sync') |
|
697
|
|
|
|
|
|
|
or return; |
|
698
|
2
|
|
|
|
|
85
|
foreach my $who (qw(user group)) { |
|
699
|
4
|
100
|
|
|
|
8
|
foreach my $wval (sort keys %{$roles->{$base}->{$bval}->{$who} || {}}) { |
|
|
4
|
|
|
|
|
25
|
|
|
700
|
2
|
50
|
|
|
|
14
|
my $wid = $self->api_identity_get_id($who, $wval, error => 1, msg => 'for role sync') |
|
701
|
|
|
|
|
|
|
or return; |
|
702
|
2
|
|
|
|
|
5
|
foreach my $role (@{$roles->{$base}->{$bval}->{$who}->{$wval}}) { |
|
|
2
|
|
|
|
|
73
|
|
|
703
|
3
|
50
|
|
|
|
22
|
my $rid = $self->api_identity_get_id('role', $role, error => 1, msg => 'for role sync') |
|
704
|
|
|
|
|
|
|
or return; |
|
705
|
3
|
|
|
|
|
19
|
$items->{"${base}s/$bid/${who}s/$wid/roles/$rid"} = 1; |
|
706
|
|
|
|
|
|
|
} |
|
707
|
|
|
|
|
|
|
} |
|
708
|
|
|
|
|
|
|
}; |
|
709
|
|
|
|
|
|
|
}; |
|
710
|
|
|
|
|
|
|
}; |
|
711
|
|
|
|
|
|
|
|
|
712
|
1
|
|
|
|
|
7
|
my $wanted = Set::Scalar->new(keys %$items); |
|
713
|
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
my $rest = sub { |
|
715
|
2
|
|
|
2
|
|
74
|
my ($urls, $method, $tagmethod) = @_; |
|
716
|
|
|
|
|
|
|
|
|
717
|
2
|
50
|
|
|
|
5
|
if (@$urls) { |
|
718
|
2
|
|
|
|
|
10
|
$self->verbose("roles sync: going to $tagmethod @$urls"); |
|
719
|
|
|
|
|
|
|
} else { |
|
720
|
0
|
|
|
|
|
0
|
$self->verbose("roles sync: nothing to $tagmethod"); |
|
721
|
0
|
|
|
|
|
0
|
return 1; |
|
722
|
|
|
|
|
|
|
}; |
|
723
|
|
|
|
|
|
|
|
|
724
|
2
|
|
|
|
|
498
|
foreach my $url (@$urls) { |
|
725
|
3
|
|
|
|
|
11
|
my $resp = $self->rest(mkrequest($url, $method, version => 'v3', service => 'identity')); |
|
726
|
3
|
50
|
|
|
|
12
|
if ($resp) { |
|
727
|
3
|
50
|
|
|
|
8
|
if ($tagstore) { |
|
728
|
3
|
|
|
|
|
9
|
my $tag = "ROLE_" . encode_base64url($url); |
|
729
|
3
|
50
|
|
|
|
41
|
if (!$tagstore->$tagmethod($tag)) { |
|
730
|
0
|
|
|
|
|
0
|
$tagstore->error("Failed to $tagmethod tag $tag to tagstore. ". |
|
731
|
|
|
|
|
|
|
"See previous error where to add the tag to continue"); |
|
732
|
0
|
|
|
|
|
0
|
return; |
|
733
|
|
|
|
|
|
|
} |
|
734
|
|
|
|
|
|
|
} |
|
735
|
|
|
|
|
|
|
} else { |
|
736
|
0
|
|
|
|
|
0
|
$self->error("Failed to sync role $method $url"); |
|
737
|
0
|
|
|
|
|
0
|
return; |
|
738
|
|
|
|
|
|
|
} |
|
739
|
|
|
|
|
|
|
} |
|
740
|
2
|
|
|
|
|
7
|
return 1 |
|
741
|
1
|
|
|
|
|
93
|
}; |
|
742
|
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
# Add new ones |
|
744
|
1
|
|
|
|
|
2
|
my @tocreate = sort @{$wanted - $existing}; |
|
|
1
|
|
|
|
|
4
|
|
|
745
|
1
|
50
|
|
|
|
243
|
$rest->(\@tocreate, 'PUT', 'add') or return; |
|
746
|
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
# Delete unknown |
|
748
|
1
|
|
|
|
|
2
|
my @toremove = sort @{$existing - $wanted}; |
|
|
1
|
|
|
|
|
4
|
|
|
749
|
1
|
50
|
|
|
|
219
|
$rest->(\@toremove, 'DELETE', 'delete') or return; |
|
750
|
|
|
|
|
|
|
|
|
751
|
1
|
|
|
|
|
11
|
return 1; |
|
752
|
|
|
|
|
|
|
} |
|
753
|
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
=pod |
|
755
|
|
|
|
|
|
|
|
|
756
|
|
|
|
|
|
|
=back |
|
757
|
|
|
|
|
|
|
|
|
758
|
|
|
|
|
|
|
=cut |
|
759
|
|
|
|
|
|
|
|
|
760
|
|
|
|
|
|
|
1; |