gooddata 2.1.7-java → 2.1.12-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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/.gdc-ii-config.yaml +3 -0
  3. data/.rubocop.yml +7 -0
  4. data/.travis.yml +2 -4
  5. data/CHANGELOG.md +39 -0
  6. data/Dockerfile +17 -4
  7. data/Dockerfile.jruby +4 -4
  8. data/Dockerfile.ruby +5 -4
  9. data/SDK_VERSION +1 -1
  10. data/VERSION +1 -1
  11. data/bin/provision.sh +2 -0
  12. data/bin/release.sh +2 -0
  13. data/bin/rollout.sh +2 -0
  14. data/bin/run_brick.rb +31 -7
  15. data/bin/test_projects_cleanup.rb +10 -2
  16. data/bin/user_filters.sh +2 -0
  17. data/ci.rake +1 -1
  18. data/ci/bigquery/pom.xml +54 -0
  19. data/ci/redshift/pom.xml +73 -0
  20. data/ci/snowflake/pom.xml +57 -0
  21. data/dev-gooddata-sso.pub.encrypted +40 -40
  22. data/gdc_fossa_lcm.yaml +2 -0
  23. data/gdc_fossa_ruby_sdk.yaml +4 -0
  24. data/gooddata.gemspec +7 -3
  25. data/k8s/charts/lcm-bricks/Chart.yaml +1 -1
  26. data/k8s/charts/lcm-bricks/templates/prometheus/alertingRules.yaml +22 -12
  27. data/lcm.rake +14 -0
  28. data/lib/gooddata/bricks/middleware/execution_result_middleware.rb +68 -0
  29. data/lib/gooddata/bricks/middleware/logger_middleware.rb +2 -1
  30. data/lib/gooddata/bricks/middleware/mask_logger_decorator.rb +5 -1
  31. data/lib/gooddata/bricks/pipeline.rb +7 -0
  32. data/lib/gooddata/cloud_resources/bigquery/bigquery_client.rb +86 -0
  33. data/lib/gooddata/cloud_resources/bigquery/drivers/.gitkeepme +0 -0
  34. data/lib/gooddata/cloud_resources/cloud_resouce_factory.rb +28 -0
  35. data/lib/gooddata/cloud_resources/cloud_resource_client.rb +24 -0
  36. data/lib/gooddata/cloud_resources/cloud_resources.rb +12 -0
  37. data/lib/gooddata/cloud_resources/redshift/drivers/log4j.properties +15 -0
  38. data/lib/gooddata/cloud_resources/redshift/redshift_client.rb +101 -0
  39. data/lib/gooddata/cloud_resources/snowflake/drivers/.gitkeepme +0 -0
  40. data/lib/gooddata/cloud_resources/snowflake/snowflake_client.rb +84 -0
  41. data/lib/gooddata/exceptions/invalid_env_error.rb +15 -0
  42. data/lib/gooddata/helpers/data_helper.rb +10 -0
  43. data/lib/gooddata/helpers/global_helpers.rb +4 -0
  44. data/lib/gooddata/helpers/global_helpers_params.rb +4 -7
  45. data/lib/gooddata/lcm/actions/collect_clients.rb +6 -6
  46. data/lib/gooddata/lcm/actions/collect_dynamic_schedule_params.rb +6 -6
  47. data/lib/gooddata/lcm/actions/collect_segment_clients.rb +4 -1
  48. data/lib/gooddata/lcm/actions/collect_segments.rb +1 -2
  49. data/lib/gooddata/lcm/actions/collect_users_brick_users.rb +7 -6
  50. data/lib/gooddata/lcm/actions/create_segment_masters.rb +5 -3
  51. data/lib/gooddata/lcm/actions/set_master_project.rb +76 -0
  52. data/lib/gooddata/lcm/actions/synchronize_clients.rb +1 -1
  53. data/lib/gooddata/lcm/actions/synchronize_etls_in_segment.rb +1 -2
  54. data/lib/gooddata/lcm/actions/synchronize_ldm.rb +17 -2
  55. data/lib/gooddata/lcm/actions/synchronize_user_filters.rb +23 -3
  56. data/lib/gooddata/lcm/actions/synchronize_users.rb +50 -30
  57. data/lib/gooddata/lcm/actions/update_release_table.rb +7 -1
  58. data/lib/gooddata/lcm/exceptions/lcm_execution_error.rb +16 -0
  59. data/lib/gooddata/lcm/helpers/release_table_helper.rb +16 -8
  60. data/lib/gooddata/lcm/lcm2.rb +27 -5
  61. data/lib/gooddata/models/domain.rb +17 -15
  62. data/lib/gooddata/models/execution.rb +0 -1
  63. data/lib/gooddata/models/execution_detail.rb +0 -1
  64. data/lib/gooddata/models/profile.rb +33 -11
  65. data/lib/gooddata/models/project.rb +45 -25
  66. data/lib/gooddata/models/project_creator.rb +2 -0
  67. data/lib/gooddata/models/schedule.rb +0 -1
  68. data/lib/gooddata/rest/client.rb +2 -2
  69. data/lib/gooddata/rest/connection.rb +5 -3
  70. data/rubydev_public.gpg.encrypted +51 -51
  71. data/rubydev_secret_keys.gpg.encrypted +109 -109
  72. metadata +38 -15
  73. data/lib/gooddata/extensions/hash.rb +0 -18
@@ -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
@@ -105,6 +106,14 @@ module GoodData
105
106
  UpdateReleaseTable
106
107
  ],
107
108
 
109
+ release_set_master_project: [
110
+ EnsureReleaseTable,
111
+ CollectDataProduct,
112
+ SegmentsFilter,
113
+ SetMasterProject,
114
+ UpdateReleaseTable
115
+ ],
116
+
108
117
  provision: [
109
118
  EnsureReleaseTable,
110
119
  CollectDataProduct,
@@ -270,8 +279,14 @@ module GoodData
270
279
 
271
280
  GoodData.gd_logger.brick = mode
272
281
 
282
+ final_mode = if params.set_master_project && mode == 'release'
283
+ 'release_set_master_project'
284
+ else
285
+ mode
286
+ end
287
+
273
288
  # Get actions for mode specified
274
- actions = get_mode_actions(mode)
289
+ actions = get_mode_actions(final_mode)
275
290
 
276
291
  if params.actions
277
292
  actions = params.actions.map do |action|
@@ -304,6 +319,12 @@ module GoodData
304
319
  skip_actions.include?(action.name.split('::').last)
305
320
  end
306
321
 
322
+ sync_mode = params.fetch(:sync_mode, nil)
323
+ if mode == 'users' && %w[add_to_organization remove_from_organization].include?(sync_mode)
324
+ actions = actions.reject do |action|
325
+ %w[CollectDataProduct CollectSegments].include?(action.name.split('::').last)
326
+ end
327
+ end
307
328
  check_unused_params(actions, params)
308
329
  print_action_names(mode, actions)
309
330
 
@@ -357,10 +378,11 @@ module GoodData
357
378
 
358
379
  if errors.any?
359
380
  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
381
+ if strict_mode
382
+ raise GoodData::LcmExecutionError.new(errors[0][:err], error_message)
383
+ else
384
+ GoodData.logger.error(error_message)
385
+ end
364
386
  end
365
387
 
366
388
  result
@@ -222,24 +222,26 @@ module GoodData
222
222
  domain = client.domain(domain)
223
223
  if id == :all
224
224
  GoodData.logger.warn("Retrieving all users from domain #{domain.name}")
225
- Enumerator.new do |y|
226
- page_limit = opts[:page_limit] || 1000
227
- offset = opts[:offset] || 0
228
- loop do
229
- begin
230
- tmp = client(opts).get("#{domain.uri}/users", params: { offset: offset, limit: page_limit })
231
- end
232
-
233
- tmp['accountSettings']['items'].each do |user_data|
234
- user = client.create(GoodData::Profile, user_data)
235
- y << user if user
236
- end
237
- break if tmp['accountSettings']['items'].count < page_limit
238
- offset += page_limit
225
+ all_users = []
226
+ page_limit = opts[:page_limit] || 1000
227
+ offset = opts[:offset] || 0
228
+ loop do
229
+ begin
230
+ tmp = client(opts).get("#{domain.uri}/users", params: { offset: offset, limit: page_limit })
239
231
  end
232
+
233
+ tmp['accountSettings']['items'].each do |user_data|
234
+ user = client.create(GoodData::Profile, user_data)
235
+ all_users << user if user
236
+ end
237
+ break if tmp['accountSettings']['items'].count < page_limit
238
+
239
+ offset += page_limit
240
240
  end
241
+
242
+ all_users
241
243
  else
242
- find_user_by_login(domain, id)
244
+ find_user_by_login(domain, id, opts)
243
245
  end
244
246
  end
245
247
 
@@ -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
@@ -1546,26 +1546,28 @@ module GoodData
1546
1546
  # @return [Array<GoodData::User>] List of users
1547
1547
  def users(opts = {})
1548
1548
  client = client(opts)
1549
- Enumerator.new do |y|
1550
- offset = opts[:offset] || 0
1551
- limit = opts[:limit] || 1_000
1552
- loop do
1553
- tmp = client.get("/gdc/projects/#{pid}/users", params: { offset: offset, limit: limit })
1554
- tmp['users'].each do |user_data|
1555
- user = client.create(GoodData::Membership, user_data, project: self)
1556
-
1557
- if opts[:all]
1558
- y << user
1559
- elsif opts[:disabled]
1560
- y << user if user && user.disabled?
1561
- else
1562
- y << user if user && user.enabled?
1563
- end
1549
+ all_users = []
1550
+ offset = opts[:offset] || 0
1551
+ limit = opts[:limit] || 1_000
1552
+ loop do
1553
+ tmp = client.get("/gdc/projects/#{pid}/users", params: { offset: offset, limit: limit })
1554
+ tmp['users'].each do |user_data|
1555
+ user = client.create(GoodData::Membership, user_data, project: self)
1556
+
1557
+ if opts[:all]
1558
+ all_users << user
1559
+ elsif opts[:disabled]
1560
+ all_users << user if user&.disabled?
1561
+ else
1562
+ all_users << user if user&.enabled?
1564
1563
  end
1565
- break if tmp['users'].count < limit
1566
- offset += limit
1567
1564
  end
1565
+ break if tmp['users'].count < limit
1566
+
1567
+ offset += limit
1568
1568
  end
1569
+
1570
+ all_users
1569
1571
  end
1570
1572
 
1571
1573
  alias_method :members, :users
@@ -1604,14 +1606,19 @@ module GoodData
1604
1606
  def import_users(new_users, options = {})
1605
1607
  role_list = roles
1606
1608
  users_list = users
1607
- new_users = new_users.map { |x| ((x.is_a?(Hash) && x[:user] && x[:user].to_hash.merge(role: x[:role])) || x.to_hash).tap { |u| u[:login].downcase! } }
1608
1609
 
1609
1610
  GoodData.logger.warn("Importing users to project (#{pid})")
1611
+ new_users = new_users.map { |x| ((x.is_a?(Hash) && x[:user] && x[:user].to_hash.merge(role: x[:role])) || x.to_hash).tap { |u| u[:login].downcase! } }
1612
+ # First check that if groups are provided we have them set up
1613
+ user_groups_cache, change_groups = check_groups(new_users.map(&:to_hash).flat_map { |u| u[:user_group] || [] }.uniq, options[:user_groups_cache], options)
1610
1614
 
1611
- whitelisted_new_users, whitelisted_users = whitelist_users(new_users.map(&:to_hash), users_list, options[:whitelists])
1615
+ unless change_groups.empty?
1616
+ new_users.each do |user|
1617
+ user[:user_group].map! { |e| change_groups[e].nil? ? e : change_groups[e] }
1618
+ end
1619
+ end
1612
1620
 
1613
- # First check that if groups are provided we have them set up
1614
- user_groups_cache = check_groups(new_users.map(&:to_hash).flat_map { |u| u[:user_group] || [] }.uniq, options[:user_groups_cache], options)
1621
+ whitelisted_new_users, whitelisted_users = whitelist_users(new_users.map(&:to_hash), users_list, options[:whitelists])
1615
1622
 
1616
1623
  # conform the role on list of new users so we can diff them with the users coming from the project
1617
1624
  diffable_new_with_default_role = whitelisted_new_users.map do |u|
@@ -1758,7 +1765,20 @@ module GoodData
1758
1765
  def check_groups(specified_groups, user_groups_cache = nil, options = {})
1759
1766
  current_user_groups = user_groups if user_groups_cache.nil? || user_groups_cache.empty?
1760
1767
  groups = current_user_groups.map(&:name)
1761
- missing_groups = specified_groups - groups
1768
+ missing_groups = []
1769
+ change_groups = {}
1770
+ specified_groups.each do |group|
1771
+ found_group = groups.find { |name| name.casecmp(group).zero? }
1772
+ if found_group.nil?
1773
+ missing_groups << group
1774
+ else
1775
+ # Change groups when they have similar group name with difference of case sensitivity
1776
+ if found_group != group
1777
+ change_groups[group] = found_group
1778
+ GoodData.logger.warn("Group with name #{group} is existed in project with name #{found_group}.")
1779
+ end
1780
+ end
1781
+ end
1762
1782
  if options[:create_non_existing_user_groups]
1763
1783
  missing_groups.each do |g|
1764
1784
  GoodData.logger.info("Creating group #{g}")
@@ -1771,7 +1791,7 @@ module GoodData
1771
1791
  "#{groups.join(',')} and you asked for #{missing_groups.join(',')}"
1772
1792
  end
1773
1793
  end
1774
- current_user_groups
1794
+ [current_user_groups, change_groups]
1775
1795
  end
1776
1796
 
1777
1797
  # Update user
@@ -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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
- 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
- 5mNdHsf2XhwDE/bvLoIuPtK45voCGL+Fz4YBtSdJsyrbbGixtFWZOY38fqA9
42
- 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
- 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
- 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
- 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
- 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
- EkCr7vOnYL4bRKFzy6jru+906rxJwtZQP7fajTvjBmTJ5QLY8CNrpUMU8iOc
48
- 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
- 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
- 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
- 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