gooddata 2.1.8-java → 2.1.9-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -0
  3. data/.travis.yml +1 -1
  4. data/Dockerfile +9 -4
  5. data/Dockerfile.jruby +4 -4
  6. data/Dockerfile.ruby +5 -4
  7. data/SDK_VERSION +1 -1
  8. data/VERSION +1 -1
  9. data/bin/provision.sh +2 -0
  10. data/bin/release.sh +2 -0
  11. data/bin/rollout.sh +2 -0
  12. data/bin/run_brick.rb +28 -7
  13. data/bin/test_projects_cleanup.rb +4 -0
  14. data/bin/user_filters.sh +2 -0
  15. data/ci.rake +1 -1
  16. data/dev-gooddata-sso.pub.encrypted +40 -40
  17. data/gooddata.gemspec +5 -1
  18. data/lcm.rake +10 -0
  19. data/lib/gooddata/bricks/middleware/execution_result_middleware.rb +68 -0
  20. data/lib/gooddata/bricks/middleware/logger_middleware.rb +2 -1
  21. data/lib/gooddata/bricks/middleware/mask_logger_decorator.rb +5 -1
  22. data/lib/gooddata/bricks/pipeline.rb +7 -0
  23. data/lib/gooddata/cloud_resources/cloud_resouce_factory.rb +28 -0
  24. data/lib/gooddata/cloud_resources/cloud_resource_client.rb +24 -0
  25. data/lib/gooddata/cloud_resources/cloud_resources.rb +12 -0
  26. data/lib/gooddata/cloud_resources/redshift/drivers/log4j.properties +15 -0
  27. data/lib/gooddata/cloud_resources/redshift/redshift_client.rb +100 -0
  28. data/lib/gooddata/exceptions/invalid_env_error.rb +15 -0
  29. data/lib/gooddata/helpers/data_helper.rb +10 -0
  30. data/lib/gooddata/helpers/global_helpers.rb +4 -0
  31. data/lib/gooddata/helpers/global_helpers_params.rb +4 -7
  32. data/lib/gooddata/lcm/actions/collect_segment_clients.rb +4 -1
  33. data/lib/gooddata/lcm/actions/collect_segments.rb +1 -2
  34. data/lib/gooddata/lcm/actions/create_segment_masters.rb +5 -3
  35. data/lib/gooddata/lcm/actions/synchronize_clients.rb +1 -1
  36. data/lib/gooddata/lcm/actions/synchronize_etls_in_segment.rb +1 -2
  37. data/lib/gooddata/lcm/actions/synchronize_ldm.rb +10 -2
  38. data/lib/gooddata/lcm/actions/synchronize_user_filters.rb +22 -2
  39. data/lib/gooddata/lcm/actions/synchronize_users.rb +19 -0
  40. data/lib/gooddata/lcm/actions/update_release_table.rb +7 -1
  41. data/lib/gooddata/lcm/exceptions/lcm_execution_error.rb +16 -0
  42. data/lib/gooddata/lcm/helpers/release_table_helper.rb +16 -8
  43. data/lib/gooddata/lcm/lcm2.rb +6 -4
  44. data/lib/gooddata/models/execution.rb +0 -1
  45. data/lib/gooddata/models/execution_detail.rb +0 -1
  46. data/lib/gooddata/models/profile.rb +33 -11
  47. data/lib/gooddata/models/project.rb +2 -2
  48. data/lib/gooddata/models/project_creator.rb +2 -0
  49. data/lib/gooddata/models/schedule.rb +0 -1
  50. data/lib/gooddata/rest/client.rb +2 -2
  51. data/lib/gooddata/rest/connection.rb +5 -3
  52. data/rubydev_public.gpg.encrypted +51 -51
  53. data/rubydev_secret_keys.gpg.encrypted +109 -109
  54. metadata +12 -5
  55. data/lib/gooddata/extensions/hash.rb +0 -18
@@ -190,6 +190,9 @@ module GoodData
190
190
  create_non_existing_user_groups: create_non_existing_user_groups,
191
191
  user_groups_cache: nil
192
192
  }
193
+ GoodData.gd_logger.info("Synchronizing in mode=#{mode}, data_rows=#{new_users.size}")
194
+
195
+ GoodData.logger.info("Synchronizing in mode \"#{mode}\"")
193
196
  results = case mode
194
197
  when 'add_to_organization'
195
198
  domain.create_users(new_users.uniq { |u| u[:login] || u[:email] })
@@ -197,6 +200,8 @@ module GoodData
197
200
  user_ids = new_users.uniq { |u| u[:login] || u[:email] }.map { |u| u[:login] || u[:email] }
198
201
  users = user_ids.map { |u| domain.users(u, client: client) }
199
202
  params.gdc_logger.warn "Deleting #{users.count} users from domain #{domain_name}"
203
+
204
+ GoodData.gd_logger.info("Synchronizing in mode=#{mode}, domain=#{domain_name}, data_rows=#{users.count}")
200
205
  users.map(&:delete)
201
206
  when 'sync_project'
202
207
  project.import_users(new_users, common_params)
@@ -204,6 +209,8 @@ module GoodData
204
209
  new_users.group_by { |u| u[:pid] }.flat_map do |project_id, users|
205
210
  begin
206
211
  project = client.projects(project_id)
212
+
213
+ GoodData.gd_logger.info("Synchronizing in mode=#{mode}, project_id=#{project_id}, data_rows=#{users.count}")
207
214
  project.import_users(users, common_params)
208
215
  rescue RestClient::ResourceNotFound
209
216
  fail "Project \"#{project_id}\" was not found. Please check your project ids in the source file"
@@ -215,6 +222,8 @@ module GoodData
215
222
  end
216
223
  when 'sync_one_project_based_on_pid'
217
224
  filtered_users = new_users.select { |u| u[:pid] == project.pid }
225
+
226
+ GoodData.gd_logger.info("Synchronizing in mode=#{mode}, data_rows=#{filtered_users.count}")
218
227
  project.import_users(filtered_users, common_params)
219
228
  when 'sync_one_project_based_on_custom_id'
220
229
  filter_value = UserBricksHelper.resolve_client_id(domain, project, data_product)
@@ -235,6 +244,7 @@ module GoodData
235
244
  end
236
245
 
237
246
  GoodData.logger.info("Project #{project.pid} will receive #{filtered_users.count} from #{new_users.count} users")
247
+ GoodData.gd_logger.info("Synchronizing in mode=#{mode}, project_id=#{project.pid}, filtered_users=#{filtered_users.count}, data_rows=#{new_users.count}")
238
248
  project.import_users(filtered_users, common_params)
239
249
  when 'sync_multiple_projects_based_on_custom_id'
240
250
  all_clients = domain.clients(:all, data_product).to_a
@@ -248,6 +258,8 @@ module GoodData
248
258
  fail "Client #{client_id} does not have project." unless project
249
259
 
250
260
  GoodData.logger.info("Project #{project.pid} of client #{client_id} will receive #{users.count} users")
261
+
262
+ GoodData.gd_logger.info("Synchronizing in mode=#{mode}, project_id=#{project.pid}, data_rows=#{users.count}")
251
263
  project.import_users(users, common_params)
252
264
  end
253
265
  when 'sync_domain_client_workspaces'
@@ -280,6 +292,8 @@ module GoodData
280
292
 
281
293
  working_client_ids << client_id.to_s
282
294
  GoodData.logger.info("Project #{project.pid} of client #{client_id} will receive #{users.count} users")
295
+
296
+ GoodData.gd_logger.info("Synchronizing in mode=#{mode}, project_id=#{project.pid}, data_rows=#{users.count}")
283
297
  project.import_users(users, common_params)
284
298
  end
285
299
 
@@ -303,13 +317,18 @@ module GoodData
303
317
  next
304
318
  end
305
319
  GoodData.logger.info("Synchronizing all users in project #{project.pid} of client #{c.client_id}")
320
+
321
+ GoodData.gd_logger.info("Synchronizing all users in project_id=#{project.pid}, client_id=#{c.client_id}")
306
322
  res += project.import_users([], common_params)
307
323
  end
308
324
  end
309
325
 
310
326
  res
311
327
  when 'sync_domain_and_project'
328
+ GoodData.gd_logger.info("Create users in mode=#{mode}, data_rows=#{new_users.count}")
312
329
  domain.create_users(new_users, ignore_failures: ignore_failures)
330
+
331
+ GoodData.gd_logger.info("Import users in mode=#{mode}, data_rows=#{new_users.count}")
313
332
  project.import_users(new_users, common_params)
314
333
  end
315
334
 
@@ -39,6 +39,7 @@ module GoodData
39
39
  class << self
40
40
  def call(params)
41
41
  client = params.gdc_gd_client
42
+ GoodData.gd_logger.info("Update release table: use_nfs=#{params.ads_client.nil?}")
42
43
 
43
44
  domain_name = params.organization || params.domain
44
45
  fail "Either organisation or domain has to be specified in params" unless domain_name
@@ -48,6 +49,7 @@ module GoodData
48
49
  segment_id = segment_in.segment_id
49
50
 
50
51
  placeholders = {
52
+ data_product_id: segment_in[:data_product_id],
51
53
  segment_id: segment_in[:segment_id],
52
54
  master_project_id: segment_in[:master_pid],
53
55
  version: segment_in[:version],
@@ -83,7 +85,11 @@ module GoodData
83
85
 
84
86
  params.ads_client.execute(query)
85
87
  else
86
- GoodData::LCM2::Helpers.update_latest_master_to_nfs(domain_id, placeholders[:segment_id], placeholders[:master_project_id], placeholders[:version])
88
+ data_product_id = placeholders[:data_product_id]
89
+ segment_id = placeholders[:segment_id]
90
+ master_pid = placeholders[:master_project_id]
91
+ version = placeholders[:version]
92
+ GoodData::LCM2::Helpers.update_latest_master_to_nfs(domain_id, data_product_id, segment_id, master_pid, version)
87
93
  end
88
94
  end
89
95
  end
@@ -0,0 +1,16 @@
1
+ # Copyright (c) 2010-2019 GoodData Corporation. All rights reserved.
2
+ # This source code is licensed under the BSD-style license found in the
3
+ # LICENSE file in the root directory of this source tree.
4
+
5
+ module GoodData
6
+ class LcmExecutionError < RuntimeError
7
+ DEFAULT_MSG = 'Error during lcm execution'
8
+
9
+ attr_reader :summary_error
10
+
11
+ def initialize(summary_error, message = DEFAULT_MSG)
12
+ super(message)
13
+ @summary_error = summary_error
14
+ end
15
+ end
16
+ end
@@ -8,7 +8,7 @@ module GoodData
8
8
  module LCM2
9
9
  class Helpers
10
10
  DEFAULT_TABLE_NAME = 'LCM_RELEASE'
11
- DEFAULT_NFS_DIRECTORY = 'release-tables'
11
+ DEFAULT_NFS_DIRECTORY = '/release-tables'
12
12
 
13
13
  class << self
14
14
  def latest_master_project_from_ads(release_table_name, ads_client, segment_id)
@@ -25,22 +25,30 @@ module GoodData
25
25
  sorted.last
26
26
  end
27
27
 
28
- def latest_master_project_from_nfs(domain_id, segment_id)
29
- data = GoodData::Helpers::Csv.read_as_hash(path_to_release_table_file(domain_id, segment_id))
30
- data.sort_by { |master| master[:version] }
28
+ def latest_master_project_from_nfs(domain_id, data_product_id, segment_id)
29
+ file_path = path_to_release_table_file(domain_id, data_product_id, segment_id)
30
+ data = GoodData::Helpers::Csv.read_as_hash(file_path)
31
+ latest_master_project = data.sort_by { |master| master[:version] }
31
32
  .reverse.first
33
+
34
+ version_info = latest_master_project ? "master_pid=#{latest_master_project[:master_project_id]} version=#{latest_master_project[:version]}" : ""
35
+ GoodData.gd_logger.info "Getting latest master project: file=#{file_path} domain=#{domain_id} data_product=#{data_product_id} segment=#{segment_id} #{version_info}"
36
+ latest_master_project
32
37
  end
33
38
 
34
- def update_latest_master_to_nfs(domain_id, segment_id, master_pid, version)
39
+ def update_latest_master_to_nfs(domain_id, data_product_id, segment_id, master_pid, version)
40
+ file_path = path_to_release_table_file(domain_id, data_product_id, segment_id)
41
+ GoodData.gd_logger.info "Updating release table: file=#{file_path} domain=#{domain_id} data_product=#{data_product_id} segment=#{segment_id} master_pid=#{master_pid} version=#{version}" # rubocop:disable Metrics/LineLength
35
42
  GoodData::Helpers::Csv.ammend_line(
36
- path_to_release_table_file(domain_id, segment_id),
43
+ file_path,
37
44
  master_project_id: master_pid,
38
45
  version: version
39
46
  )
40
47
  end
41
48
 
42
- def path_to_release_table_file(domain_id, segment_id)
43
- [DEFAULT_NFS_DIRECTORY, domain_id, segment_id + '.csv'].join('/')
49
+ def path_to_release_table_file(domain_id, data_prod_id, segment_id)
50
+ nsf_directory = ENV['RELEASE_TABLE_NFS_DIRECTORY'] || DEFAULT_NFS_DIRECTORY
51
+ [nsf_directory, domain_id, data_prod_id + '-' + segment_id + '.csv'].join('/')
44
52
  end
45
53
  end
46
54
  end
@@ -18,6 +18,7 @@ require 'active_support/core_ext/hash/compact'
18
18
  require_relative 'actions/actions'
19
19
  require_relative 'dsl/dsl'
20
20
  require_relative 'helpers/helpers'
21
+ require_relative 'exceptions/lcm_execution_error'
21
22
 
22
23
  using TrueExtensions
23
24
  using FalseExtensions
@@ -357,10 +358,11 @@ module GoodData
357
358
 
358
359
  if errors.any?
359
360
  error_message = JSON.pretty_generate(errors)
360
- GoodData.logger.error(error_message)
361
-
362
- # Fail whole execution if there is any failed action
363
- fail(error_message) if strict_mode
361
+ if strict_mode
362
+ raise GoodData::LcmExecutionError.new(errors[0][:err], error_message)
363
+ else
364
+ GoodData.logger.error(error_message)
365
+ end
364
366
  end
365
367
 
366
368
  result
@@ -5,7 +5,6 @@
5
5
  # LICENSE file in the root directory of this source tree.
6
6
 
7
7
  require_relative '../rest/resource'
8
- require_relative '../extensions/hash'
9
8
 
10
9
  module GoodData
11
10
  class Execution < Rest::Resource
@@ -5,7 +5,6 @@
5
5
  # LICENSE file in the root directory of this source tree.
6
6
 
7
7
  require_relative '../rest/resource'
8
- require_relative '../extensions/hash'
9
8
 
10
9
  module GoodData
11
10
  class ExecutionDetail < Rest::Resource
@@ -324,20 +324,42 @@ module GoodData
324
324
  end
325
325
 
326
326
  # Gets the array of projects
327
- #
327
+ # @param limit [Integer] maximum number of projects to get.
328
+ # @param offset [Integer] offset of the first project, start from 0.
328
329
  # @return [Array<GoodData::Project>] Array of project where account settings belongs to
329
- def projects(limit = nil)
330
+ def projects(limit = nil, offset = nil)
330
331
  url = @json['accountSetting']['links']['projects']
331
- query_params = ''
332
- if !limit.nil? && limit.is_a?(Integer) && limit > 0
333
- limit = [limit, 500].min
334
- query_params += "limit=#{limit}"
335
- end
336
- url += "?#{query_params}" unless query_params.empty?
337
- projects = client.get url
338
- projects['projects'].map do |project|
339
- client.create(GoodData::Project, project)
332
+
333
+ all_projects = []
334
+
335
+ raise ArgumentError, 'Params limit and offset are expected' if !offset.nil? && limit.nil?
336
+
337
+ if limit.nil?
338
+ url += "?limit=500"
339
+ loop do
340
+ projects = client.get url
341
+ projects['projects']['items'].each do |project|
342
+ all_projects << client.create(GoodData::Project, project)
343
+ end
344
+ if !projects['projects']['paging'].nil? && !projects['projects']['paging']['next'].nil?
345
+ url = projects['projects']['paging']['next']
346
+ else
347
+ break
348
+ end
349
+ end
350
+ else
351
+ limit = [limit, 500].min if limit.is_a?(Integer) && limit > 0
352
+
353
+ url += "?limit=#{limit}"
354
+ url += "&offset=#{offset}" if !offset.nil? && offset.is_a?(Integer) && offset > 0
355
+
356
+ projects = client.get url
357
+ projects['projects']['items'].each do |project|
358
+ all_projects << client.create(GoodData::Project, project)
359
+ end
340
360
  end
361
+
362
+ all_projects
341
363
  end
342
364
 
343
365
  # Saves object if dirty, clears dirty flag
@@ -67,9 +67,9 @@ module GoodData
67
67
  class << self
68
68
  # Returns an array of all projects accessible by
69
69
  # current user
70
- def all(opts = { client: GoodData.connection }, limit = nil)
70
+ def all(opts = { client: GoodData.connection }, limit = nil, offset = nil)
71
71
  c = GoodData.get_client(opts)
72
- c.user.projects(limit)
72
+ c.user.projects(limit, offset)
73
73
  end
74
74
 
75
75
  # Returns a Project object identified by given string
@@ -47,6 +47,8 @@ module GoodData
47
47
  unless response
48
48
  maql_diff_params = [:includeGrain]
49
49
  maql_diff_params << :excludeFactRule if opts[:exclude_fact_rule]
50
+ maql_diff_params << :includeDeprecated if opts[:include_deprecated]
51
+
50
52
  maql_diff_time = Benchmark.realtime do
51
53
  response = project.maql_diff(blueprint: bp, params: maql_diff_params)
52
54
  end
@@ -5,7 +5,6 @@
5
5
  # LICENSE file in the root directory of this source tree.
6
6
 
7
7
  require_relative '../rest/resource'
8
- require_relative '../extensions/hash'
9
8
  require_relative '../mixins/rest_resource'
10
9
  require_relative '../helpers/global_helpers'
11
10
 
@@ -188,11 +188,11 @@ module GoodData
188
188
  end
189
189
  end
190
190
 
191
- def projects(id = :all, limit = nil)
191
+ def projects(id = :all, limit = nil, offset = nil)
192
192
  if limit.nil?
193
193
  GoodData::Project[id, client: self]
194
194
  else
195
- GoodData::Project.all({ client: self }, limit)
195
+ GoodData::Project.all({ client: self }, limit, offset)
196
196
  end
197
197
  end
198
198
 
@@ -30,6 +30,7 @@ module GoodData
30
30
  LOGIN_PATH = '/gdc/account/login'
31
31
  TOKEN_PATH = '/gdc/account/token'
32
32
  KEYS_TO_SCRUB = [:password, :verifyPassword, :authorizationToken]
33
+ API_LEVEL = 2
33
34
 
34
35
  ID_LENGTH = 16
35
36
 
@@ -307,7 +308,6 @@ module GoodData
307
308
  # Remove when TT sent in headers. Currently we need to parse from body
308
309
  merge_headers!(:x_gdc_authtt => GoodData::Helpers.get_path(response, %w(userToken token)))
309
310
  rescue Exception => e # rubocop:disable RescueException
310
- GoodData.logger.error(e.message)
311
311
  raise e
312
312
  end
313
313
  end
@@ -350,6 +350,9 @@ module GoodData
350
350
  profile method.to_s.upcase, uri, request_id, stats_on do
351
351
  b = proc do
352
352
  params = fresh_request_params(request_id).merge(options)
353
+
354
+ params['X-GDC-VERSION'] = API_LEVEL
355
+
353
356
  case method
354
357
  when :get
355
358
  @server[uri].get(params, &user_block)
@@ -514,8 +517,7 @@ module GoodData
514
517
  begin
515
518
  request.execute
516
519
  rescue => e
517
- GoodData.logger.error("Error when uploading file #{filename}")
518
- raise e
520
+ raise "Error when uploading file #{filename}. Error: #{e}"
519
521
  end
520
522
  end
521
523
 
@@ -1,51 +1,51 @@
1
- eP6F85n9m2Dcr/lpjq3WaVmYq46J1yDgRut4w/mWReFkW2nPobJv224rqLY+
2
- JLHp6/mxJrrPlSR96TqIHvkQTyNIaZj9seFXLfmbdxg4uJOzlBP2rUEKB/DJ
3
- ywGjOEgpiec93Tkn1flHCmvsegXdU059EO/KCx/5jaBsOaNLP9lYfBnLjzmH
4
- E9vvpWp6V+qodHEpWDoUsXo5S2YuW5yN9Ht7WcRyXWHxhbQbPuclMWcnbuqu
5
- ieN08hNNRcqdoOYGnj4obLX+DKOiAId3f6mezJc1nhtfvf1ZNk1qXTO4D82V
6
- Rp3RJVB5wvyQoHuSn3+nycPITGxfb2xMwqwxn8FZMBhUjpFiBmHfnGRCu30u
7
- fQxKk/qyRnfYLfrqzgbD+i8k+xCqfuP5RGSKaRu0gzJGxLAJoIUCUWePvr5c
8
- UAOd9nijD1DnndpMYGsfKlvt6Ebv4yDewOjPDF5sNSGK6hxDDOvh2VE6ZzKc
9
- euLgWGR2XZvNVE6kjpq31+4mjBvvI3l+8YE9QoPEGA8XP33QtRjbGZFx3zgS
10
- XEYeKgSBaWCLeeWcSfHqXQ0sCCmcgiUr9nb9keLhllv1h4z79z69KKopc5iF
11
- YYEkhAczwI00vKNu5SBiYvDqOJu50P+/eJG6P4IGqq+S95TXIrxpOIlcIs22
12
- CqdCFtqsYwLRJJGI6X8OyTNinsiINmVsKynsZzb3WKJJuM53GxTe51oHvPWp
13
- 8YjhK2jAgH4xn1Uztgr/tRKel8ckosY9wZmvlOURiCxE112MPcjew1sZ7Bgx
14
- DvyRbSrgk4jPiaZ/iTbkqeksnmCILpxToTw/8m7owB48vqgmhM4qry9Kaq4x
15
- qsSdB3bLj/D80VF5+Gnhr0AjCnZts1DPsjELVrTrlMsOc168+rUI8UJLD8Ci
16
- hFECXlDD7QXjh3XJoD6PD5xP/sJg2iYeve7SC7qCddku1ouzOfDLKmi2Hrra
17
- nGdD/+1ggkjCTbessARa49LfTbvxIQWva+sySfcm6/xvS1iREQZwAPBQKqEL
18
- wgGqwTJhkdDlrBzEyx2tMXt3ZcfiQUlp/F1bp4ROb2bh5rFAcPbcdPfd3Enp
19
- spX8K0KP3HjoSnTgILhLAw8wbKuepYBcwQPX+7s3fyoPad+zE/4uq51NrFAF
20
- 2u1QLivw/8pAElx935RcGcCxI7BeHW2OEpTiP1oIllFjlxW50fR95N3AZ3eb
21
- H4gXR8e7gkW2ZaBfOzEIdHE08yTB8iUHsVQkWznH0ZUIAsv2hRCxfSs2/qKB
22
- kQglwdngAtvAxJzIdb7jgpZ/s1Tn4WbqtManVrnXwFteiWnOVSltPUcoKhPl
23
- 6U+CncomSzpnktlb0xBP6XxFX06EkDrAeky14NiT6FOjoFQOedYKvNLkmDBc
24
- SQt7ck8XHDRggkDJrWfn50ZF0p6RZC6FGU+PsFQszM96b1QAzx3lx8A+tmOj
25
- R1NOmWw28QKPlItGe+Cu9UTSHoHng4yKwEzgH10KiX8q9rosJT3V7DVQvWOx
26
- s3XBg2U/YeXXsmvfOX5k5kLRvsH2KKuEnTFx2lRl9bNgs0vWSkJha60wTcjQ
27
- GuVZ/y+rVMQM/tIjnI+IfZ35knz2vnRTLk0u6R2kJenTXahlwhvjwmol9y6H
28
- eixTfA4sdOUno8pvu7Ur8NVNpUsb/iZ/acf3nEOauqUCrixleZPcqc2vVtWk
29
- MxGgqSmaihZi8ZHD1Pryf3DwYB9ssSuV3BKthsHnsjOcVWksn2vdiXtogDwt
30
- kxnP/TLjbTZk3oAOIjyYzvEo9kVjb51qZO17a9rklo7CE89EBJZEyTdWpfhX
31
- cYczntRjhaFjBqFfJ3JhpyMvDfc2NN9C7dZy2f5BPuNI7QoJLgFxaz6qj8SW
32
- PhJcxmqLuqyqTS2VNAA5IdOASnt9LycV5xkGoUzSGc0efIsOSCKeVu2NqVqg
33
- HIBk3QV7Fk0/bv7U5ZN9nMPWgui/+2lbYTgqAC+wK6q/BRhZYY5jZBGbtPe/
34
- ukQUkh9w7WLgdJ9MblqyxHWa6T1ZPh9a1Agz2XCv6SQHj6qlk9KX3LvOba9F
35
- l7Za2d7Jv5ozLcpkhPW1ZAWAbtPG4T/Y7XzhSf+2SjZEIoKJv3VcL0lC+IE/
36
- ZUhGE6lNQLuesPGu3vKX6VuoeVj3PyMaWtzMIqrxVzrTCUlynRrPY2OM+SnS
37
- ftcthAOGApQ5Z9wUhMUDXDkPYMCny2joAK32XgFqZjq75Zljux4QgggEKTMY
38
- YXsfBHVNQ48zciP55IIMJ1oeC0gmLx1AqtA8R9j8frTnmwR1cTQtk4ydSDVt
39
- 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
- IP018L/HsijWKot2BZlidcZGKLtba2VtYPgQqhotrBe3sp6G8PlSKwcpJWkM
41
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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