gooddata 2.1.8-java → 2.1.9-java

Sign up to get free protection for your applications and to get access to all the features.
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
- QKunn0dl35ZUG9XNS+LLnxV4aOjSqB9Hv8fgcIQFWSIz4HmLIienlVoPTsvY
40
- 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
- 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
- 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
- 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
- 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
- pzt7F+ZD6wIL4uu9qyVbIhk1ntP3qvaA1Y0lSA2qrKMLxWK0giJlebGYD2AH
46
- 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
- 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
- 9gmTEfc6M3zXJlq00Ws01mk8ef4Fh9PI1PgFE/3rNSm7ZzrKao5C8aMySdpL
49
- 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
- 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
- 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