gooddata 2.1.11 → 2.1.17

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/.gdc-ii-config.yaml +1 -1
  3. data/.sonar.settings +4 -0
  4. data/.travis.yml +4 -3
  5. data/CHANGELOG.md +34 -0
  6. data/Dockerfile +9 -3
  7. data/LICENSE +30 -22
  8. data/LICENSE.rb +1 -1
  9. data/README.md +19 -1
  10. data/Rakefile +8 -1
  11. data/SDK_VERSION +1 -1
  12. data/VERSION +1 -1
  13. data/bin/run_brick.rb +3 -0
  14. data/bin/test_projects_cleanup.rb +7 -3
  15. data/ci/postgresql/pom.xml +57 -0
  16. data/dev-gooddata-sso.pub.encrypted +40 -40
  17. data/gdc_fossa_lcm.yaml +2 -0
  18. data/gdc_fossa_ruby_sdk.yaml +5 -0
  19. data/gooddata.gemspec +4 -5
  20. data/k8s/charts/lcm-bricks/Chart.yaml +1 -1
  21. data/k8s/charts/lcm-bricks/templates/prometheus/alertingRules.yaml +32 -12
  22. data/lib/gooddata.rb +2 -0
  23. data/lib/gooddata/cloud_resources/{cloud_resouce_factory.rb → cloud_resource_factory.rb} +8 -0
  24. data/lib/gooddata/cloud_resources/cloud_resources.rb +1 -1
  25. data/lib/gooddata/cloud_resources/postgresql/drivers/.gitkeepme +0 -0
  26. data/lib/gooddata/cloud_resources/postgresql/postgresql_client.rb +107 -0
  27. data/lib/gooddata/commands/scaffold.rb +9 -10
  28. data/lib/gooddata/core/nil_logger.rb +3 -1
  29. data/lib/gooddata/helpers/data_helper.rb +1 -2
  30. data/lib/gooddata/helpers/global_helpers.rb +6 -5
  31. data/lib/gooddata/helpers/global_helpers_params.rb +2 -2
  32. data/lib/gooddata/lcm/actions/base_action.rb +0 -2
  33. data/lib/gooddata/lcm/actions/collect_tagged_objects.rb +2 -1
  34. data/lib/gooddata/lcm/actions/migrate_gdc_date_dimension.rb +116 -0
  35. data/lib/gooddata/lcm/actions/synchronize_ldm.rb +10 -1
  36. data/lib/gooddata/lcm/actions/synchronize_user_filters.rb +4 -0
  37. data/lib/gooddata/lcm/actions/synchronize_users.rb +7 -6
  38. data/lib/gooddata/lcm/lcm2.rb +1 -2
  39. data/lib/gooddata/lcm/types/base_type.rb +0 -2
  40. data/lib/gooddata/mixins/md_object_query.rb +8 -6
  41. data/lib/gooddata/models/blueprint/project_blueprint.rb +0 -2
  42. data/lib/gooddata/models/client.rb +14 -12
  43. data/lib/gooddata/models/data_source.rb +664 -0
  44. data/lib/gooddata/models/domain.rb +1 -2
  45. data/lib/gooddata/models/from_wire.rb +1 -0
  46. data/lib/gooddata/models/metadata/scheduled_mail.rb +1 -1
  47. data/lib/gooddata/models/process.rb +11 -3
  48. data/lib/gooddata/models/project.rb +128 -19
  49. data/lib/gooddata/models/user_filters/user_filter_builder.rb +0 -1
  50. data/lib/gooddata/models/user_group.rb +0 -1
  51. data/lib/gooddata/rest/connection.rb +6 -4
  52. data/rubydev_public.gpg.encrypted +51 -51
  53. data/rubydev_secret_keys.gpg.encrypted +109 -109
  54. metadata +17 -25
  55. data/DEPENDENCIES.md +0 -880
@@ -50,7 +50,8 @@ module GoodData
50
50
  if transfer_all
51
51
  vizs = MdObject.query('visualizationObject', MdObject, client: development_client, project: from_project)
52
52
  viz_widgets = MdObject.query('visualizationWidget', MdObject, client: development_client, project: from_project)
53
- objects = (from_project.reports.to_a + from_project.metrics.to_a + from_project.variables.to_a + vizs.to_a + viz_widgets.to_a).map(&:uri)
53
+ theme = MdObject.query('theme', MdObject, client: development_client, project: from_project)
54
+ objects = (from_project.reports.to_a + from_project.metrics.to_a + from_project.variables.to_a + vizs.to_a + viz_widgets.to_a + theme.to_a).map(&:uri)
54
55
  elsif production_tags.any?
55
56
  objects = from_project.find_by_tag(production_tags)
56
57
  end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+ # (C) 2019-2020 GoodData Corporation
3
+ require_relative 'base_action'
4
+
5
+ # Migrate date dimension urn:gooddata:date or urn:custom:date to urn:custom_v2:date
6
+ module GoodData
7
+ module LCM2
8
+ class MigrateGdcDateDimension < BaseAction
9
+ DESCRIPTION = 'Migrate Gdc Date Dimension'
10
+ DATE_DIMENSION_CUSTOM_V2 = 'urn:custom_v2:date'
11
+ DATE_DIMENSION_OLD = %w[urn:gooddata:date urn:custom:date]
12
+
13
+ PARAMS = define_params(self) do
14
+ description 'Client Used for Connecting to GD'
15
+ param :gdc_gd_client, instance_of(Type::GdClientType), required: true
16
+
17
+ description 'Specifies how to synchronize LDM and resolve possible conflicts'
18
+ param :synchronize_ldm, instance_of(Type::SynchronizeLDM), required: false, default: 'diff_against_master_with_fallback'
19
+
20
+ description 'Synchronization Info'
21
+ param :synchronize, array_of(instance_of(Type::SynchronizationInfoType)), required: true, generated: true
22
+ end
23
+
24
+ RESULT_HEADER = %i[from to status]
25
+
26
+ class << self
27
+ def call(params)
28
+ results = []
29
+ params.synchronize.map do |segment_info|
30
+ result = migrate_date_dimension(params, segment_info)
31
+ results.concat(result)
32
+ end
33
+
34
+ {
35
+ results: results
36
+ }
37
+ end
38
+
39
+ def migrate_date_dimension(params, segment_info)
40
+ results = []
41
+ client = params.gdc_gd_client
42
+ latest_blueprint = segment_info[:from_blueprint]
43
+ # don't migrate when latest master doesn't contain custom v2 date.
44
+ return results unless contain_v2?(latest_blueprint)
45
+
46
+ previous_blueprint = segment_info[:previous_master]&.blueprint
47
+ # check latest master and previous master
48
+ master_upgrade_datasets = get_upgrade_dates(latest_blueprint, previous_blueprint) if params[:synchronize_ldm].downcase == 'diff_against_master' && previous_blueprint
49
+ unless master_upgrade_datasets&.empty?
50
+ segment_info[:to].pmap do |entry|
51
+ pid = entry[:pid]
52
+ to_project = client.projects(pid) || fail("Invalid 'to' project specified - '#{pid}'")
53
+ to_blueprint = to_project.blueprint
54
+ upgrade_datasets = get_upgrade_dates(latest_blueprint, to_blueprint)
55
+ next if upgrade_datasets.empty?
56
+
57
+ message = get_upgrade_message(upgrade_datasets)
58
+
59
+ results << {
60
+ from: segment_info[:from],
61
+ to: pid,
62
+ status: to_project.upgrade_custom_v2(message)
63
+ }
64
+ end
65
+ end
66
+
67
+ results
68
+ end
69
+
70
+ def get_upgrade_dates(src_blueprint, dest_blueprint)
71
+ dest_dates = get_date_dimensions(dest_blueprint) if dest_blueprint
72
+ src_dates = get_date_dimensions(src_blueprint) if src_blueprint
73
+
74
+ return false if dest_dates.empty? || src_dates.empty?
75
+
76
+ upgrade_datasets = []
77
+ dest_dates.each do |dest|
78
+ src_dim = get_date_dimension(src_blueprint, dest[:id])
79
+ next unless src_dim
80
+
81
+ upgrade_datasets << src_dim[:identifier] if upgrade?(src_dim, dest) && src_dim[:identifier]
82
+ end
83
+
84
+ upgrade_datasets
85
+ end
86
+
87
+ def get_upgrade_message(upgrade_datasets)
88
+ {
89
+ upgrade: {
90
+ dateDatasets: {
91
+ upgrade: "exact",
92
+ datasets: upgrade_datasets
93
+ }
94
+ }
95
+ }
96
+ end
97
+
98
+ def upgrade?(src_dim, dest_dim)
99
+ src_dim[:urn] == DATE_DIMENSION_CUSTOM_V2 && DATE_DIMENSION_OLD.any? { |e| dest_dim[:urn] == e }
100
+ end
101
+
102
+ def contain_v2?(blueprint)
103
+ get_date_dimensions(blueprint).any? { |e| e[:urn] == DATE_DIMENSION_CUSTOM_V2 }
104
+ end
105
+
106
+ def get_date_dimension(blueprint, id)
107
+ GoodData::Model::ProjectBlueprint.find_date_dimension(blueprint, id)
108
+ end
109
+
110
+ def get_date_dimensions(blueprint)
111
+ GoodData::Model::ProjectBlueprint.date_dimensions(blueprint)
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -49,6 +49,8 @@ module GoodData
49
49
  param :include_deprecated, instance_of(Type::BooleanType), required: false, default: false
50
50
  end
51
51
 
52
+ RESULT_HEADER = %i[from to status]
53
+
52
54
  class << self
53
55
  def call(params)
54
56
  results = []
@@ -76,9 +78,9 @@ module GoodData
76
78
  include_deprecated = params.include_deprecated.to_b
77
79
  from_pid = segment_info[:from]
78
80
  from = params.development_client.projects(from_pid) || fail("Invalid 'from' project specified - '#{from_pid}'")
79
-
80
81
  GoodData.logger.info "Creating Blueprint, project: '#{from.title}', PID: #{from_pid}"
81
82
  blueprint = from.blueprint(include_ca: params.include_computed_attributes.to_b)
83
+ segment_info[:from_blueprint] = blueprint
82
84
  maql_diff = nil
83
85
  previous_master = segment_info[:previous_master]
84
86
  diff_against_master = %w(diff_against_master_with_fallback diff_against_master)
@@ -89,6 +91,13 @@ module GoodData
89
91
  maql_diff_params << :excludeFactRule if exclude_fact_rule
90
92
  maql_diff_params << :includeDeprecated if include_deprecated
91
93
  maql_diff = previous_master.maql_diff(blueprint: blueprint, params: maql_diff_params)
94
+ chunks = maql_diff['projectModelDiff']['updateScripts']
95
+ if chunks.empty?
96
+ GoodData.logger.info "Synchronize LDM to clients will not proceed in mode \
97
+ '#{params[:synchronize_ldm].downcase}' due to no LDM changes in the new master project. \
98
+ If you had changed LDM of clients manually, please use mode 'diff_against_clients' \
99
+ to force synchronize LDM to clients"
100
+ end
92
101
  end
93
102
 
94
103
  segment_info[:to] = segment_info[:to].pmap do |entry|
@@ -167,6 +167,10 @@ module GoodData
167
167
  fail "Client id cannot be empty" if client_id.blank?
168
168
 
169
169
  c = all_clients.detect { |specific_client| specific_client.id == client_id }
170
+ if c.nil?
171
+ params.gdc_logger.warn "Client #{client_id} is not found"
172
+ next
173
+ end
170
174
  if params.segments && !segment_uris.include?(c.segment_uri)
171
175
  params.gdc_logger.warn "Client #{client_id} is outside segments_filter #{params.segments}"
172
176
  next
@@ -182,11 +182,11 @@ module GoodData
182
182
  # value of a project id in the data since he does not know it upfront
183
183
  # and we cannot influence its value.
184
184
  common_params = {
185
- domain: domain,
186
- whitelists: whitelists,
187
- ignore_failures: ignore_failures,
188
- remove_users_from_project: remove_users_from_project,
189
- do_not_touch_users_that_are_not_mentioned: do_not_touch_users_that_are_not_mentioned,
185
+ domain: domain,
186
+ whitelists: whitelists,
187
+ ignore_failures: ignore_failures,
188
+ remove_users_from_project: remove_users_from_project,
189
+ do_not_touch_users_that_are_not_mentioned: do_not_touch_users_that_are_not_mentioned,
190
190
  create_non_existing_user_groups: create_non_existing_user_groups,
191
191
  user_groups_cache: nil
192
192
  }
@@ -198,7 +198,8 @@ module GoodData
198
198
  domain.create_users(new_users.uniq { |u| u[:login] || u[:email] })
199
199
  when 'remove_from_organization'
200
200
  user_ids = new_users.uniq { |u| u[:login] || u[:email] }.map { |u| u[:login] || u[:email] }
201
- users = user_ids.map { |u| domain.users(u, client: client) }
201
+ users = user_ids.map { |u| domain.users(u, client: client) }.reject(&:nil?)
202
+ params.gdc_logger.info "#{user_ids.count - users.count} users were not found (or were deleted) in domain #{domain_name}" if user_ids.count > users.count
202
203
  params.gdc_logger.warn "Deleting #{users.count} users from domain #{domain_name}"
203
204
 
204
205
  GoodData.gd_logger.info("Synchronizing in mode=#{mode}, domain=#{domain_name}, data_rows=#{users.count}")
@@ -13,8 +13,6 @@ require 'gooddata/extensions/integer'
13
13
  require 'gooddata/extensions/string'
14
14
  require 'gooddata/extensions/nil'
15
15
 
16
- require 'active_support/core_ext/hash/compact'
17
-
18
16
  require_relative 'actions/actions'
19
17
  require_relative 'dsl/dsl'
20
18
  require_relative 'helpers/helpers'
@@ -138,6 +136,7 @@ module GoodData
138
136
  EnsureTechnicalUsersDomain,
139
137
  EnsureTechnicalUsersProject,
140
138
  SynchronizeLdm,
139
+ MigrateGdcDateDimension,
141
140
  SynchronizeClients,
142
141
  SynchronizeComputedAttributes,
143
142
  CollectDymanicScheduleParams,
@@ -8,8 +8,6 @@
8
8
  require_relative '../dsl/dsl'
9
9
  require_relative '../helpers/helpers'
10
10
 
11
- require 'active_support/core_ext/hash/compact'
12
-
13
11
  require 'gooddata/extensions/class'
14
12
 
15
13
  module GoodData
@@ -1,12 +1,14 @@
1
1
  # encoding: UTF-8
2
2
  #
3
- # Copyright (c) 2010-2017 GoodData Corporation. All rights reserved.
3
+ # Copyright (c) 2010-2021 GoodData Corporation. All rights reserved.
4
4
  # This source code is licensed under the BSD-style license found in the
5
5
  # LICENSE file in the root directory of this source tree.
6
6
 
7
7
  module GoodData
8
8
  module Mixin
9
9
  module MdObjectQuery
10
+ ERROR_MESSAGE_NO_PROJECT = 'No :project specified'
11
+
10
12
  # Method intended to get all objects of that type in a specified project
11
13
  #
12
14
  # @param options [Hash] the options hash
@@ -38,7 +40,7 @@ module GoodData
38
40
  fail ArgumentError, 'No :client specified' if client.nil?
39
41
 
40
42
  p = options[:project]
41
- fail ArgumentError, 'No :project specified' if p.nil?
43
+ fail ArgumentError, ERROR_MESSAGE_NO_PROJECT if p.nil?
42
44
 
43
45
  project = GoodData::Project[p, options]
44
46
  fail ArgumentError, 'Wrong :project specified' if project.nil?
@@ -98,10 +100,10 @@ module GoodData
98
100
  # Returns which objects uses this MD resource
99
101
  def usedby(uri, key = nil, opts = { :client => GoodData.connection, :project => GoodData.project })
100
102
  p = opts[:project]
101
- fail ArgumentError, 'No :project specified' if p.nil?
103
+ fail ArgumentError, ERROR_MESSAGE_NO_PROJECT if p.nil?
102
104
 
103
105
  project = GoodData::Project[p, opts]
104
- fail ArgumentError, 'No :project specified' if project.nil?
106
+ fail ArgumentError, ERROR_MESSAGE_NO_PROJECT if project.nil?
105
107
 
106
108
  dependency("#{project.md['usedby2']}/#{uri_obj_id(uri)}", key, opts)
107
109
  end
@@ -111,10 +113,10 @@ module GoodData
111
113
  # Returns which objects this MD resource uses
112
114
  def using(uri, key = nil, opts = { :client => GoodData.connection, :project => GoodData.project })
113
115
  p = opts[:project]
114
- fail ArgumentError, 'No :project specified' if p.nil?
116
+ fail ArgumentError, ERROR_MESSAGE_NO_PROJECT if p.nil?
115
117
 
116
118
  project = GoodData::Project[p, opts]
117
- fail ArgumentError, 'No :project specified' if project.nil?
119
+ fail ArgumentError, ERROR_MESSAGE_NO_PROJECT if project.nil?
118
120
 
119
121
  dependency("#{project.md['using2']}/#{uri_obj_id(uri)}", key, opts)
120
122
  end
@@ -2,8 +2,6 @@
2
2
  # This source code is licensed under the BSD-style license found in the
3
3
  # LICENSE file in the root directory of this source tree.
4
4
 
5
- require 'active_support/core_ext/hash/compact'
6
-
7
5
  module GoodData
8
6
  module Model
9
7
  class ProjectBlueprint
@@ -40,20 +40,22 @@ module GoodData
40
40
  if id == :all
41
41
  tenants_uri = base_uri(domain, data_product)
42
42
  tenants_uri += "?segment=#{CGI.escape(segment.segment_id)}" if segment
43
- Enumerator.new do |y|
44
- loop do
45
- res = client.get tenants_uri
46
- res['clients']['paging']['next']
47
- res['clients']['items'].each do |i|
48
- p = i['client']['project']
49
- tenant = client.create(GoodData::Client, i.merge('domain' => domain))
50
- tenant.project = p
51
- y << tenant
52
- end
53
- url = res['clients']['paging']['next']
54
- break unless url
43
+
44
+ all_clients = []
45
+ loop do
46
+ res = client.get tenants_uri
47
+ res['clients']['paging']['next']
48
+ res['clients']['items'].each do |i|
49
+ p = i['client']['project']
50
+ tenant = client.create(GoodData::Client, i.merge('domain' => domain))
51
+ tenant.project = p
52
+ all_clients << tenant
55
53
  end
54
+ url = res['clients']['paging']['next']
55
+ break unless url
56
56
  end
57
+
58
+ all_clients
57
59
  else
58
60
  id = id.respond_to?(:client_id) ? id.client_id : id
59
61
  tenant_uri = base_uri(domain, data_product)
@@ -0,0 +1,664 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+ #
4
+ # Copyright (c) 2010-2021 GoodData Corporation. All rights reserved.
5
+ # This source code is licensed under the BSD-style license found in the
6
+ # LICENSE file in the root directory of this source tree.
7
+
8
+ require_relative '../rest/resource'
9
+
10
+ module GoodData
11
+ class DataSource < Rest::Resource
12
+ attr_accessor :connection_info
13
+
14
+ DATA_SOURCES_URL = '/gdc/dataload/dataSources'
15
+ SNOWFLAKE = 'snowflake'
16
+ REDSHIFT = 'redshift'
17
+ BIGQUERY = 'bigQuery'
18
+ GENERIC = 'generic'
19
+ S3 = 's3'
20
+ ADS = 'ads'
21
+ ERROR_MESSAGE_NO_SCHEMA = 'Data source schema has to be provided'
22
+
23
+ class << self
24
+ # Get all data sources or get a specify data source from data source identify
25
+ # Expected parameter value:
26
+ # - :all return all data sources
27
+ # - :data_source_id return a data source with the specify data source identify
28
+ def [](id = :all, options = { client: GoodData.client })
29
+ c = GoodData.get_client(options)
30
+
31
+ if id == :all
32
+ data = c.get(DATA_SOURCES_URL)
33
+ data['dataSources']['items'].map do |ds_data|
34
+ c.create(DataSource, ds_data)
35
+ end
36
+ else
37
+ c.create(DataSource, c.get("#{DATA_SOURCES_URL}/#{id}"))
38
+ end
39
+ end
40
+
41
+ # Get a specify data source from data source identify
42
+ #
43
+ # @param [String] id Data source identify
44
+ # @return [DataSource] Data source corresponding in backend or throw exception if the data source identify doesn't exist
45
+ def from_id(id, options = { client: GoodData.client })
46
+ DataSource[id, options]
47
+ end
48
+
49
+ # Get a specify data source from data source alias
50
+ #
51
+ # @param [String] data_source_alias Data source alias
52
+ # @return [DataSource] Data source corresponding in backend or throw exception if the data source alias doesn't exist
53
+ def from_alias(data_source_alias, options = { client: GoodData.client })
54
+ data_sources = all(options)
55
+ result = data_sources.find do |data_source|
56
+ data_source.alias == data_source_alias
57
+ end
58
+ fail "Data source alias '#{data_source_alias}' has not found" unless result
59
+
60
+ result
61
+ end
62
+
63
+ # Get all data sources
64
+ def all(options = { client: GoodData.client })
65
+ DataSource[:all, options]
66
+ end
67
+
68
+ # Create data source from json
69
+ # Expected keys:
70
+ # - :name (mandatory)
71
+ # - :alias (optional)
72
+ # - :prefix (optional)
73
+ # - :connectionInfo (mandatory)
74
+ # - :client (mandatory)
75
+ def create(opts)
76
+ ds_name = opts[:name]
77
+ ds_alias = opts[:alias]
78
+ ds_prefix = opts[:prefix]
79
+ ds_connection_info = opts[:connectionInfo]
80
+
81
+ GoodData.logger.info "Creating data source '#{ds_name}'"
82
+ fail ArgumentError, 'Data source name has to be provided' if ds_name.nil? || ds_name.blank?
83
+ fail ArgumentError, 'Data source connection info has to be provided' if ds_connection_info.nil?
84
+
85
+ json = {
86
+ 'dataSource' => {
87
+ 'name' => ds_name,
88
+ 'connectionInfo' => ds_connection_info
89
+ }
90
+ }
91
+ json['dataSource']['alias'] = ds_alias if ds_alias
92
+ json['dataSource']['prefix'] = ds_prefix if ds_prefix
93
+
94
+ # Create data source
95
+ c = GoodData.get_client(opts)
96
+ res = c.post(DATA_SOURCES_URL, json)
97
+
98
+ # create the public facing object
99
+ c.create(DataSource, res)
100
+ end
101
+ end
102
+
103
+ # Save data source to backend. The saving will validate existing data source name and connection info. So need set
104
+ # values for them.
105
+ #
106
+ # Input info:
107
+ # - :name (mandatory)
108
+ # - :alias (optional)
109
+ # - :prefix (optional)
110
+ # - :connectionInfo (mandatory)
111
+ #
112
+ # Return: create data source in backend and return data source object corresponding with data source in backend.
113
+ def save
114
+ validate
115
+ validate_connection_info
116
+ if saved?
117
+ update_obj_json = client.put(uri, to_update_payload)
118
+ @json = update_obj_json
119
+ else
120
+ res = client.post(DATA_SOURCES_URL, to_update_payload)
121
+ fail 'Unable to create new Data Source' if res.nil?
122
+
123
+ @json = res
124
+ end
125
+ @connection_info = build_connection_info
126
+ self
127
+ end
128
+
129
+ def delete
130
+ saved? ? client.delete(uri) : nil
131
+ end
132
+
133
+ def initialize(json)
134
+ super
135
+ @json = json
136
+ validate
137
+ @connection_info = build_connection_info
138
+ end
139
+
140
+ def saved?
141
+ !uri.blank?
142
+ end
143
+
144
+ def name
145
+ @json['dataSource']['name']
146
+ end
147
+
148
+ def name=(new_name)
149
+ @json['dataSource']['name'] = new_name
150
+ end
151
+
152
+ def alias
153
+ @json['dataSource']['alias']
154
+ end
155
+
156
+ def alias=(new_alias)
157
+ @json['dataSource']['alias'] = new_alias
158
+ end
159
+
160
+ def prefix
161
+ @json['dataSource']['prefix']
162
+ end
163
+
164
+ def prefix=(new_prefix)
165
+ @json['dataSource']['prefix'] = new_prefix
166
+ end
167
+
168
+ def uri
169
+ @json['dataSource']['links']['self'] if @json && @json['dataSource'] && @json['dataSource']['links']
170
+ end
171
+
172
+ def id
173
+ uri.split('/')[-1]
174
+ end
175
+
176
+ def is(type)
177
+ @json['dataSource']['connectionInfo'][type]
178
+ end
179
+
180
+ private
181
+
182
+ def build_connection_info
183
+ return snowflake_connection_info if is(SNOWFLAKE)
184
+ return redshift_connection_info if is(REDSHIFT)
185
+ return bigquery_connection_info if is(BIGQUERY)
186
+ return generic_connection_info if is(GENERIC)
187
+ return s3_connection_info if is(S3)
188
+ return ads_connection_info if is(ADS)
189
+
190
+ # In case don't know data source type then support get or set directly json data for connection info
191
+ ConnectionInfo.new(@json['dataSource']['connectionInfo'])
192
+ end
193
+
194
+ def support_connection_info(connection_info)
195
+ [SnowflakeConnectionInfo, RedshiftConnectionInfo, BigQueryConnectionInfo,
196
+ GenericConnectionInfo, S3ConnectionInfo, AdsConnectionInfo].include? connection_info.class
197
+ end
198
+
199
+ def snowflake_connection_info
200
+ return nil unless is(SNOWFLAKE)
201
+
202
+ SnowflakeConnectionInfo.new(@json['dataSource']['connectionInfo'])
203
+ end
204
+
205
+ def redshift_connection_info
206
+ return nil unless is(REDSHIFT)
207
+
208
+ RedshiftConnectionInfo.new(@json['dataSource']['connectionInfo'])
209
+ end
210
+
211
+ def bigquery_connection_info
212
+ return nil unless is(BIGQUERY)
213
+
214
+ BigQueryConnectionInfo.new(@json['dataSource']['connectionInfo'])
215
+ end
216
+
217
+ def generic_connection_info
218
+ return nil unless is(GENERIC)
219
+
220
+ GenericConnectionInfo.new(@json['dataSource']['connectionInfo'])
221
+ end
222
+
223
+ def s3_connection_info
224
+ return nil unless is(S3)
225
+
226
+ S3ConnectionInfo.new(@json['dataSource']['connectionInfo'])
227
+ end
228
+
229
+ def ads_connection_info
230
+ return nil unless is(ADS)
231
+
232
+ AdsConnectionInfo.new(@json['dataSource']['connectionInfo'])
233
+ end
234
+
235
+ def to_update_payload
236
+ json_data = {
237
+ 'dataSource' => {
238
+ 'name' => name,
239
+ 'connectionInfo' => @connection_info.to_update_payload
240
+ }
241
+ }
242
+ json_data['dataSource']['alias'] = self.alias if self.alias
243
+ json_data['dataSource']['prefix'] = prefix if prefix
244
+ json_data
245
+ end
246
+
247
+ def validate
248
+ fail 'Invalid data source json data' unless @json['dataSource']
249
+ fail 'Data source connection info has to be provided' unless @json['dataSource']['connectionInfo']
250
+ fail 'Data source name has to be provided' if name.nil? || name.blank?
251
+ end
252
+
253
+ def validate_connection_info
254
+ @connection_info.validate
255
+ end
256
+
257
+ class ConnectionInfo < Rest::Resource
258
+ def initialize(connection_info_json)
259
+ @json = connection_info_json
260
+ end
261
+
262
+ def connection_info
263
+ @json
264
+ end
265
+
266
+ def connection_info=(connection_info_json)
267
+ @json = connection_info_json
268
+ end
269
+
270
+ def to_update_payload
271
+ @json
272
+ end
273
+
274
+ # Abstract function
275
+ def validate
276
+ end
277
+ end
278
+
279
+ class SnowflakeConnectionInfo < ConnectionInfo
280
+ def initialize(connection_info_json)
281
+ @json = connection_info_json[GoodData::DataSource::SNOWFLAKE]
282
+ end
283
+
284
+ def url
285
+ @json['url']
286
+ end
287
+
288
+ def url=(new_url)
289
+ @json['url'] = new_url
290
+ end
291
+
292
+ def user_name
293
+ @json['authentication']['basic']['userName'] if @json && @json['authentication'] && @json['authentication']['basic']
294
+ end
295
+
296
+ def user_name=(new_user_name)
297
+ @json['authentication']['basic']['userName'] = new_user_name
298
+ end
299
+
300
+ def password
301
+ @json['authentication']['basic']['password'] if @json && @json['authentication'] && @json['authentication']['basic']
302
+ end
303
+
304
+ def password=(new_password)
305
+ @json['authentication']['basic']['password'] = new_password
306
+ end
307
+
308
+ def database
309
+ @json['database']
310
+ end
311
+
312
+ def database=(new_database)
313
+ @json['database'] = new_database
314
+ end
315
+
316
+ def schema
317
+ @json['schema']
318
+ end
319
+
320
+ def schema=(new_schema)
321
+ @json['schema'] = new_schema
322
+ end
323
+
324
+ def warehouse
325
+ @json['warehouse']
326
+ end
327
+
328
+ def warehouse=(new_warehouse)
329
+ @json['warehouse'] = new_warehouse
330
+ end
331
+
332
+ def to_update_payload
333
+ {
334
+ 'snowflake' => {
335
+ 'url' => url,
336
+ 'authentication' => {
337
+ 'basic' => {
338
+ 'userName' => user_name,
339
+ 'password' => password
340
+ }
341
+ },
342
+ 'database' => database,
343
+ 'schema' => schema,
344
+ 'warehouse' => warehouse
345
+ }
346
+ }
347
+ end
348
+
349
+ def validate
350
+ fail 'Data source url has to be provided' if url.nil? || url.blank?
351
+ fail 'Data source database has to be provided' if database.nil? || database.blank?
352
+ fail ERROR_MESSAGE_NO_SCHEMA if schema.nil? || schema.blank?
353
+ fail 'Data source warehouse has to be provided' if warehouse.nil? || warehouse.blank?
354
+ fail 'Data source username has to be provided' if user_name.nil? || user_name.blank?
355
+ end
356
+ end
357
+
358
+ class RedshiftConnectionInfo < ConnectionInfo
359
+ def initialize(connection_info_json)
360
+ @json = connection_info_json[GoodData::DataSource::REDSHIFT]
361
+ end
362
+
363
+ def url
364
+ @json['url']
365
+ end
366
+
367
+ def url=(new_url)
368
+ @json['url'] = new_url
369
+ end
370
+
371
+ def user_name
372
+ @json['authentication']['basic']['userName'] if basic_authentication
373
+ end
374
+
375
+ def user_name=(new_user_name)
376
+ @json['authentication']['basic']['userName'] = new_user_name
377
+ end
378
+
379
+ def password
380
+ @json['authentication']['basic']['password'] if basic_authentication
381
+ end
382
+
383
+ def password=(new_password)
384
+ @json['authentication']['basic']['password'] = new_password
385
+ end
386
+
387
+ def db_user
388
+ @json['authentication']['iam']['dbUser'] if iam_authentication
389
+ end
390
+
391
+ def db_user=(new_db_user)
392
+ @json['authentication']['iam']['dbUser'] = new_db_user
393
+ end
394
+
395
+ def access_key_id
396
+ @json['authentication']['iam']['accessKeyId'] if iam_authentication
397
+ end
398
+
399
+ def access_key_id=(new_access_key_id)
400
+ @json['authentication']['iam']['accessKeyId'] = new_access_key_id
401
+ end
402
+
403
+ def secret_access_key
404
+ @json['authentication']['iam']['secretAccessKey'] if iam_authentication
405
+ end
406
+
407
+ def secret_access_key=(new_secret_access_key)
408
+ @json['authentication']['iam']['secretAccessKey'] = new_secret_access_key
409
+ end
410
+
411
+ def basic_authentication
412
+ @json && @json['authentication'] && @json['authentication']['basic']
413
+ end
414
+
415
+ def iam_authentication
416
+ @json && @json['authentication'] && @json['authentication']['iam']
417
+ end
418
+
419
+ def database
420
+ @json['database']
421
+ end
422
+
423
+ def database=(new_database)
424
+ @json['database'] = new_database
425
+ end
426
+
427
+ def schema
428
+ @json['schema']
429
+ end
430
+
431
+ def schema=(new_schema)
432
+ @json['schema'] = new_schema
433
+ end
434
+
435
+ def to_update_payload
436
+ if basic_authentication
437
+ {
438
+ 'redshift' => {
439
+ 'url' => url,
440
+ 'authentication' => {
441
+ 'basic' => {
442
+ 'userName' => user_name,
443
+ 'password' => password
444
+ }
445
+ },
446
+ 'database' => database,
447
+ 'schema' => schema
448
+ }
449
+ }
450
+ else
451
+ {
452
+ 'redshift' => {
453
+ 'url' => url,
454
+ 'authentication' => {
455
+ 'iam' => {
456
+ 'dbUser' => db_user,
457
+ 'accessKeyId' => access_key_id,
458
+ 'secretAccessKey' => secret_access_key
459
+ }
460
+ },
461
+ 'database' => database,
462
+ 'schema' => schema
463
+ }
464
+ }
465
+ end
466
+ end
467
+
468
+ def validate
469
+ fail 'Data source url has to be provided' if url.nil? || url.blank?
470
+ fail 'Data source database has to be provided' if database.nil? || database.blank?
471
+ fail ERROR_MESSAGE_NO_SCHEMA if schema.nil? || schema.blank?
472
+
473
+ if basic_authentication
474
+ fail 'Data source username has to be provided' if user_name.nil? || user_name.blank?
475
+ elsif iam_authentication
476
+ fail 'Data source db_user has to be provided' if db_user.nil? || db_user.blank?
477
+ fail 'Data source access key has to be provided' if access_key_id.nil? || access_key_id.blank?
478
+ end
479
+ end
480
+ end
481
+
482
+ class BigQueryConnectionInfo < ConnectionInfo
483
+ def initialize(connection_info_json)
484
+ @json = connection_info_json[GoodData::DataSource::BIGQUERY]
485
+ end
486
+
487
+ def client_email
488
+ @json['authentication']['serviceAccount']['clientEmail'] if @json && @json['authentication'] && @json['authentication']['serviceAccount']
489
+ end
490
+
491
+ def client_email=(new_client_email)
492
+ @json['authentication']['serviceAccount']['clientEmail'] = new_client_email
493
+ end
494
+
495
+ def private_key
496
+ @json['authentication']['serviceAccount']['privateKey'] if @json && @json['authentication'] && @json['authentication']['serviceAccount']
497
+ end
498
+
499
+ def private_key=(new_private_key)
500
+ @json['authentication']['serviceAccount']['privateKey'] = new_private_key
501
+ end
502
+
503
+ def project
504
+ @json['project']
505
+ end
506
+
507
+ def project=(new_project)
508
+ @json['project'] = new_project
509
+ end
510
+
511
+ def schema
512
+ @json['schema']
513
+ end
514
+
515
+ def schema=(new_schema)
516
+ @json['schema'] = new_schema
517
+ end
518
+
519
+ def to_update_payload
520
+ {
521
+ 'bigQuery' => {
522
+ 'authentication' => {
523
+ 'serviceAccount' => {
524
+ 'clientEmail' => client_email,
525
+ 'privateKey' => private_key
526
+ }
527
+ },
528
+ 'project' => project,
529
+ 'schema' => schema
530
+ }
531
+ }
532
+ end
533
+
534
+ def validate
535
+ fail 'Data source client email has to be provided' if client_email.nil? || client_email.blank?
536
+ fail 'Data source project has to be provided' if project.nil? || project.blank?
537
+ fail ERROR_MESSAGE_NO_SCHEMA if schema.nil? || schema.blank?
538
+ end
539
+ end
540
+
541
+ class GenericConnectionInfo < ConnectionInfo
542
+ def initialize(connection_info_json)
543
+ @json = connection_info_json[GoodData::DataSource::GENERIC]
544
+ end
545
+
546
+ def params
547
+ @json['params']
548
+ end
549
+
550
+ def params=(new_params)
551
+ @json['params'] = new_params
552
+ end
553
+
554
+ def secure_params
555
+ @json['secureParams']
556
+ end
557
+
558
+ def secure_params=(new_secure_params)
559
+ @json['secureParams'] = new_secure_params
560
+ end
561
+
562
+ def to_update_payload
563
+ {
564
+ 'generic' => {
565
+ 'params' => params,
566
+ 'secureParams' => secure_params
567
+ }
568
+ }
569
+ end
570
+
571
+ def validate
572
+ end
573
+ end
574
+
575
+ class S3ConnectionInfo < ConnectionInfo
576
+ def initialize(connection_info_json)
577
+ @json = connection_info_json[GoodData::DataSource::S3]
578
+ end
579
+
580
+ def bucket
581
+ @json['bucket']
582
+ end
583
+
584
+ def bucket=(new_bucket)
585
+ @json['bucket'] = new_bucket
586
+ end
587
+
588
+ def access_key
589
+ @json['accessKey']
590
+ end
591
+
592
+ def access_key=(new_access_key)
593
+ @json['accessKey'] = new_access_key
594
+ end
595
+
596
+ def secret_key
597
+ @json['secretKey']
598
+ end
599
+
600
+ def secret_key=(new_secret_key)
601
+ @json['secretKey'] = new_secret_key
602
+ end
603
+
604
+ def server_side_encryption
605
+ @json['serverSideEncryption']
606
+ end
607
+
608
+ def server_side_encryption=(new_server_side_encryption)
609
+ @json['serverSideEncryption'] = new_server_side_encryption
610
+ end
611
+
612
+ def to_update_payload
613
+ {
614
+ 's3' => {
615
+ 'bucket' => bucket,
616
+ 'accessKey' => access_key,
617
+ 'secretKey' => secret_key,
618
+ 'serverSideEncryption' => server_side_encryption
619
+ }
620
+ }
621
+ end
622
+
623
+ def validate
624
+ fail 'S3 bucket has to be provided' if bucket.nil? || bucket.blank?
625
+ fail 'S3 access key has to be provided' if access_key.nil? || access_key.blank?
626
+ end
627
+ end
628
+
629
+ class AdsConnectionInfo < ConnectionInfo
630
+ def initialize(connection_info_json)
631
+ @json = connection_info_json[GoodData::DataSource::ADS]
632
+ end
633
+
634
+ def instance
635
+ @json['instance']
636
+ end
637
+
638
+ def instance=(new_instance)
639
+ @json['instance'] = new_instance
640
+ end
641
+
642
+ def exportable
643
+ @json['exportable']
644
+ end
645
+
646
+ def exportable=(new_exportable)
647
+ @json['exportable'] = new_exportable
648
+ end
649
+
650
+ def to_update_payload
651
+ {
652
+ 'ads' => {
653
+ 'instance' => instance,
654
+ 'exportable' => exportable
655
+ }
656
+ }
657
+ end
658
+
659
+ def validate
660
+ fail 'Data source instance has to be provided' if instance.nil? || instance.blank?
661
+ end
662
+ end
663
+ end
664
+ end