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; |