3scale_toolbox 0.10.0 → 0.11.0

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +21 -144
  3. data/lib/3scale_toolbox.rb +0 -1
  4. data/lib/3scale_toolbox/commands.rb +12 -0
  5. data/lib/3scale_toolbox/commands/3scale_command.rb +1 -1
  6. data/lib/3scale_toolbox/commands/account_command.rb +23 -0
  7. data/lib/3scale_toolbox/commands/account_command/find_command.rb +41 -0
  8. data/lib/3scale_toolbox/commands/activedocs_command.rb +32 -0
  9. data/lib/3scale_toolbox/commands/activedocs_command/apply_command.rb +127 -0
  10. data/lib/3scale_toolbox/commands/activedocs_command/create_command.rb +65 -0
  11. data/lib/3scale_toolbox/commands/activedocs_command/delete_command.rb +49 -0
  12. data/lib/3scale_toolbox/commands/activedocs_command/list_command.rb +54 -0
  13. data/lib/3scale_toolbox/commands/application_command.rb +30 -0
  14. data/lib/3scale_toolbox/commands/application_command/apply_command.rb +179 -0
  15. data/lib/3scale_toolbox/commands/application_command/create_command.rb +110 -0
  16. data/lib/3scale_toolbox/commands/application_command/delete_command.rb +57 -0
  17. data/lib/3scale_toolbox/commands/application_command/list_command.rb +124 -0
  18. data/lib/3scale_toolbox/commands/application_command/show_command.rb +72 -0
  19. data/lib/3scale_toolbox/commands/copy_command/copy_service.rb +97 -28
  20. data/lib/3scale_toolbox/commands/import_command/import_csv.rb +16 -17
  21. data/lib/3scale_toolbox/commands/import_command/openapi.rb +14 -10
  22. data/lib/3scale_toolbox/commands/import_command/openapi/create_service_step.rb +3 -2
  23. data/lib/3scale_toolbox/commands/import_command/openapi/method.rb +0 -1
  24. data/lib/3scale_toolbox/commands/import_command/openapi/step.rb +8 -1
  25. data/lib/3scale_toolbox/commands/import_command/openapi/update_service_proxy_step.rb +17 -7
  26. data/lib/3scale_toolbox/commands/methods_command.rb +4 -4
  27. data/lib/3scale_toolbox/commands/methods_command/apply_command.rb +2 -2
  28. data/lib/3scale_toolbox/commands/methods_command/create_command.rb +1 -1
  29. data/lib/3scale_toolbox/commands/metrics_command.rb +4 -4
  30. data/lib/3scale_toolbox/commands/metrics_command/apply_command.rb +2 -2
  31. data/lib/3scale_toolbox/commands/metrics_command/create_command.rb +1 -1
  32. data/lib/3scale_toolbox/commands/plans_command/apply_command.rb +7 -7
  33. data/lib/3scale_toolbox/commands/plans_command/create_command.rb +5 -5
  34. data/lib/3scale_toolbox/commands/policy_registry_command.rb +22 -0
  35. data/lib/3scale_toolbox/commands/policy_registry_command/copy_command.rb +85 -0
  36. data/lib/3scale_toolbox/commands/proxy_config_command.rb +30 -0
  37. data/lib/3scale_toolbox/commands/proxy_config_command/list_command.rb +78 -0
  38. data/lib/3scale_toolbox/commands/proxy_config_command/promote_command.rb +68 -0
  39. data/lib/3scale_toolbox/commands/proxy_config_command/show_command.rb +88 -0
  40. data/lib/3scale_toolbox/commands/service_command.rb +34 -0
  41. data/lib/3scale_toolbox/commands/service_command/apply_command.rb +77 -0
  42. data/lib/3scale_toolbox/commands/service_command/create_command.rb +59 -0
  43. data/lib/3scale_toolbox/commands/service_command/delete_command.rb +49 -0
  44. data/lib/3scale_toolbox/commands/service_command/list_command.rb +50 -0
  45. data/lib/3scale_toolbox/commands/service_command/show_command.rb +63 -0
  46. data/lib/3scale_toolbox/commands/update_command/update_service.rb +6 -3
  47. data/lib/3scale_toolbox/entities.rb +4 -0
  48. data/lib/3scale_toolbox/entities/account.rb +63 -0
  49. data/lib/3scale_toolbox/entities/activedocs.rb +88 -0
  50. data/lib/3scale_toolbox/entities/application.rb +124 -0
  51. data/lib/3scale_toolbox/entities/application_plan.rb +28 -4
  52. data/lib/3scale_toolbox/entities/base_entity.rb +44 -0
  53. data/lib/3scale_toolbox/entities/proxy_config.rb +58 -0
  54. data/lib/3scale_toolbox/entities/service.rb +42 -8
  55. data/lib/3scale_toolbox/error.rb +8 -0
  56. data/lib/3scale_toolbox/tasks.rb +2 -1
  57. data/lib/3scale_toolbox/tasks/bump_proxy_version_task.rb +32 -0
  58. data/lib/3scale_toolbox/tasks/copy_service_settings_task.rb +38 -0
  59. data/lib/3scale_toolbox/version.rb +1 -1
  60. metadata +36 -5
  61. data/lib/3scale_toolbox/tasks/update_service_settings_task.rb +0 -49
@@ -0,0 +1,44 @@
1
+ module ThreeScaleToolbox
2
+ module Entities
3
+ module Entity
4
+ PRINTABLE_VARS = %w[
5
+ id
6
+ ].freeze
7
+
8
+ VERBOSE_PRINTABLE_VARS = %w[
9
+ id
10
+ ].freeze
11
+
12
+ public_constant :PRINTABLE_VARS
13
+ public_constant :VERBOSE_PRINTABLE_VARS
14
+
15
+ attr_accessor :verbose
16
+ attr_reader :id, :attrs, :remote
17
+
18
+ def initialize(id:, remote:, attrs: nil, verbose: false)
19
+ @id = id
20
+ @remote = remote
21
+ @attrs = attrs
22
+ @verbose = verbose
23
+ end
24
+
25
+ def to_s
26
+ if @verbose
27
+ format_vars(printable_attrs: self.class.const_get(:VERBOSE_PRINTABLE_VARS, inherit: true))
28
+ else
29
+ format_vars(printable_attrs: self.class.const_get(:PRINTABLE_VARS, inherit: true))
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def format_vars(printable_attrs: nil)
36
+ print_attrs = attrs.merge({ ":id" => @id })
37
+ formatted_vars = printable_attrs.map do |attr|
38
+ "#{attr} => #{attrs[attr]}"
39
+ end
40
+ formatted_vars.join("\n")
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,58 @@
1
+ module ThreeScaleToolbox
2
+ module Entities
3
+ class ProxyConfig
4
+ class << self
5
+ def find(service:, environment:, version:)
6
+ new(service: service, environment: environment, version: version).tap(&:attrs)
7
+ rescue ThreeScale::API::HttpClient::NotFoundError
8
+ nil
9
+ end
10
+
11
+ def find_latest(service:, environment:)
12
+ proxy_cfg = service.remote.proxy_config_latest(service.id, environment)
13
+ if (errors = proxy_cfg['errors'])
14
+ raise ThreeScaleToolbox::ThreeScaleApiError.new('ProxyConfig find_latest not read', errors)
15
+ end
16
+ new(service: service, environment: environment, version: proxy_cfg["version"], attrs: proxy_cfg)
17
+ rescue ThreeScale::API::HttpClient::NotFoundError
18
+ nil
19
+ end
20
+ end
21
+
22
+ attr_reader :remote, :service, :environment, :version
23
+
24
+ def initialize(environment:, service:, version:, attrs: nil)
25
+ @remote = service.remote
26
+ @service = service
27
+ @environment = environment
28
+ @version = version
29
+ @attrs = attrs
30
+ end
31
+
32
+ def attrs
33
+ @attrs ||= proxy_config_attrs
34
+ end
35
+
36
+ def promote(to:)
37
+ res = remote.promote_proxy_config(service.id, environment, version, to)
38
+
39
+ if (errors = res['errors'])
40
+ raise ThreeScaleToolbox::ThreeScaleApiError.new('ProxyConfig not promoted', errors)
41
+ end
42
+ res
43
+ end
44
+
45
+ private
46
+
47
+ def proxy_config_attrs
48
+ proxy_cfg = remote.show_proxy_config(service.id, environment, version)
49
+
50
+ if (errors = proxy_cfg['errors'])
51
+ raise ThreeScaleToolbox::ThreeScaleApiError.new('ProxyConfig not read', errors)
52
+ end
53
+ proxy_cfg
54
+ end
55
+
56
+ end
57
+ end
58
+ end
@@ -9,10 +9,10 @@ module ThreeScaleToolbox
9
9
  public_constant :VALID_PARAMS
10
10
 
11
11
  class << self
12
- def create(remote:, service:, system_name:)
12
+ def create(remote:, service_params:)
13
13
  svc_attrs = create_service(
14
14
  remote: remote,
15
- service: copy_service_params(service, system_name)
15
+ service: filtered_service_params(service_params)
16
16
  )
17
17
  if (errors = svc_attrs['errors'])
18
18
  raise ThreeScaleToolbox::ThreeScaleApiError.new('Service has not been created', errors)
@@ -58,11 +58,8 @@ module ThreeScaleToolbox
58
58
  svc_obj
59
59
  end
60
60
 
61
- def copy_service_params(original, system_name)
62
- service_params = Helper.filter_params(VALID_PARAMS, original)
63
- service_params.tap do |hash|
64
- hash['system_name'] = system_name if system_name
65
- end
61
+ def filtered_service_params(original_params)
62
+ Helper.filter_params(VALID_PARAMS, original_params)
66
63
  end
67
64
  end
68
65
 
@@ -146,7 +143,7 @@ module ThreeScaleToolbox
146
143
  end
147
144
 
148
145
  def update(svc_attrs)
149
- new_attrs = remote.update_service id, svc_attrs
146
+ new_attrs = safe_update(svc_attrs)
150
147
  if (errors = new_attrs['errors'])
151
148
  raise ThreeScaleToolbox::ThreeScaleApiError.new('Service not updated', errors)
152
149
  end
@@ -226,6 +223,28 @@ module ThreeScaleToolbox
226
223
  new_feature
227
224
  end
228
225
 
226
+ def proxy_configs(environment)
227
+ proxy_configs_attrs = remote.proxy_config_list(id, environment)
228
+ if proxy_configs_attrs.respond_to?(:has_key?) && (errors = proxy_configs_attrs['errors'])
229
+ raise ThreeScaleToolbox::ThreeScaleApiError.new('ProxyConfigs not read', errors)
230
+ end
231
+
232
+ proxy_configs_attrs.map do |proxy_config_attrs|
233
+ Entities::ProxyConfig.new(environment: environment, service: self, version: proxy_config_attrs.fetch("version"), attrs: proxy_config_attrs)
234
+ end
235
+ end
236
+
237
+ def applications
238
+ app_attrs_list = remote.list_applications(service_id: id)
239
+ if app_attrs_list.respond_to?(:has_key?) && (errors = app_attrs_list['errors'])
240
+ raise ThreeScaleToolbox::ThreeScaleApiError.new('Service applications not read', errors)
241
+ end
242
+
243
+ app_attrs_list.map do |app_attrs|
244
+ Entities::Application.new(id: app_attrs.fetch('id'), remote: remote, attrs: app_attrs)
245
+ end
246
+ end
247
+
229
248
  private
230
249
 
231
250
  def service_attrs
@@ -236,6 +255,21 @@ module ThreeScaleToolbox
236
255
 
237
256
  svc
238
257
  end
258
+
259
+ def safe_update(svc_attrs)
260
+ new_attrs = remote.update_service id, svc_attrs
261
+
262
+ # Source and target remotes might not allow same set of deployment options
263
+ # Invalid deployment option check
264
+ # use default deployment_option
265
+ if (errors = new_attrs['errors']) &&
266
+ ThreeScaleToolbox::Helper.service_invalid_deployment_option?(errors)
267
+ svc_attrs.delete('deployment_option')
268
+ new_attrs = remote.update_service id, svc_attrs
269
+ end
270
+
271
+ new_attrs
272
+ end
239
273
  end
240
274
  end
241
275
  end
@@ -6,6 +6,14 @@ module ThreeScaleToolbox
6
6
  class InvalidUrlError < Error
7
7
  end
8
8
 
9
+ class ActiveDocsNotFoundError < Error
10
+ attr_reader :id
11
+
12
+ def initialize(id)
13
+ super("ActiveDocs with ID #{id} not found")
14
+ end
15
+ end
16
+
9
17
  class ThreeScaleApiError < Error
10
18
  attr_reader :apierrors
11
19
 
@@ -7,8 +7,9 @@ require '3scale_toolbox/tasks/copy_app_plans_task'
7
7
  require '3scale_toolbox/tasks/copy_limits_task'
8
8
  require '3scale_toolbox/tasks/destroy_mapping_rules_task'
9
9
  require '3scale_toolbox/tasks/copy_mapping_rules_task'
10
- require '3scale_toolbox/tasks/update_service_settings_task'
10
+ require '3scale_toolbox/tasks/copy_service_settings_task'
11
11
  require '3scale_toolbox/tasks/copy_policies_task'
12
12
  require '3scale_toolbox/tasks/copy_pricingrules_task'
13
13
  require '3scale_toolbox/tasks/delete_activedocs_task'
14
14
  require '3scale_toolbox/tasks/copy_activedocs_task'
15
+ require '3scale_toolbox/tasks/bump_proxy_version_task'
@@ -0,0 +1,32 @@
1
+ module ThreeScaleToolbox
2
+ module Tasks
3
+ class BumpProxyVersionTask
4
+ attr_reader :service
5
+
6
+ def initialize(service:)
7
+ @service = service
8
+ end
9
+
10
+ ##
11
+ # bumps proxy config version to propagate proxy settings updates
12
+ def call
13
+ # Proxy update is the mechanism to increase version of the proxy,
14
+ # Hence propagating (mapping rules, poicies, oidc, auth) update to
15
+ # latest proxy config, making available to gateway.
16
+
17
+ # Currently it is done always because mapping rules, at least, are always created
18
+ # So they need to be propagated
19
+ proxy_settings = {
20
+ # Adding harmless attribute to avoid empty body
21
+ # update_proxy cannot be done with empty body
22
+ # and must be done to increase proxy version
23
+ # If proxy settings have not been changed since last update,
24
+ # this request will not have effect and proxy config version will not be bumped.
25
+ service_id: service.id
26
+ }
27
+
28
+ service.update_proxy proxy_settings
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,38 @@
1
+ module ThreeScaleToolbox
2
+ module Tasks
3
+ class CopyServiceSettingsTask
4
+ include CopyTask
5
+ PARAMS_FILTER = %w[system_name id links]
6
+
7
+ def call
8
+ svc_obj = update_service source_service_settings
9
+ if (errors = svc_obj['errors'])
10
+ raise ThreeScaleToolbox::Error, "Service has not been saved. Errors: #{errors}" \
11
+ end
12
+
13
+ puts "updated service settings for service id #{source.id}..."
14
+ end
15
+
16
+ private
17
+
18
+ def source_service_settings
19
+ source.attrs.reject { |k, _| PARAMS_FILTER.include? k }
20
+ end
21
+
22
+ def update_service(service_attrs)
23
+ svc_obj = target.update service_attrs
24
+
25
+ # Source and target remotes might not allow same set of deployment options
26
+ # Invalid deployment option check
27
+ # use default deployment_option
28
+ if (errors = svc_obj['errors']) &&
29
+ ThreeScaleToolbox::Helper.service_invalid_deployment_option?(errors)
30
+ service_attrs.delete('deployment_option')
31
+ svc_obj = target.update service_attrs
32
+ end
33
+
34
+ svc_obj
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,3 +1,3 @@
1
1
  module ThreeScaleToolbox
2
- VERSION = '0.10.0'
2
+ VERSION = '0.11.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: 3scale_toolbox
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Cichra
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2019-05-14 00:00:00.000000000 Z
12
+ date: 2019-06-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -87,14 +87,14 @@ dependencies:
87
87
  requirements:
88
88
  - - "~>"
89
89
  - !ruby/object:Gem::Version
90
- version: 0.4.0
90
+ version: 0.5.0
91
91
  type: :runtime
92
92
  prerelease: false
93
93
  version_requirements: !ruby/object:Gem::Requirement
94
94
  requirements:
95
95
  - - "~>"
96
96
  - !ruby/object:Gem::Version
97
- version: 0.4.0
97
+ version: 0.5.0
98
98
  - !ruby/object:Gem::Dependency
99
99
  name: cri
100
100
  requirement: !ruby/object:Gem::Requirement
@@ -141,6 +141,19 @@ files:
141
141
  - lib/3scale_toolbox/cli/error_handler.rb
142
142
  - lib/3scale_toolbox/commands.rb
143
143
  - lib/3scale_toolbox/commands/3scale_command.rb
144
+ - lib/3scale_toolbox/commands/account_command.rb
145
+ - lib/3scale_toolbox/commands/account_command/find_command.rb
146
+ - lib/3scale_toolbox/commands/activedocs_command.rb
147
+ - lib/3scale_toolbox/commands/activedocs_command/apply_command.rb
148
+ - lib/3scale_toolbox/commands/activedocs_command/create_command.rb
149
+ - lib/3scale_toolbox/commands/activedocs_command/delete_command.rb
150
+ - lib/3scale_toolbox/commands/activedocs_command/list_command.rb
151
+ - lib/3scale_toolbox/commands/application_command.rb
152
+ - lib/3scale_toolbox/commands/application_command/apply_command.rb
153
+ - lib/3scale_toolbox/commands/application_command/create_command.rb
154
+ - lib/3scale_toolbox/commands/application_command/delete_command.rb
155
+ - lib/3scale_toolbox/commands/application_command/list_command.rb
156
+ - lib/3scale_toolbox/commands/application_command/show_command.rb
144
157
  - lib/3scale_toolbox/commands/copy_command.rb
145
158
  - lib/3scale_toolbox/commands/copy_command/copy_service.rb
146
159
  - lib/3scale_toolbox/commands/help_command.rb
@@ -191,18 +204,35 @@ files:
191
204
  - lib/3scale_toolbox/commands/plans_command/import_command.rb
192
205
  - lib/3scale_toolbox/commands/plans_command/list_command.rb
193
206
  - lib/3scale_toolbox/commands/plans_command/show_command.rb
207
+ - lib/3scale_toolbox/commands/policy_registry_command.rb
208
+ - lib/3scale_toolbox/commands/policy_registry_command/copy_command.rb
209
+ - lib/3scale_toolbox/commands/proxy_config_command.rb
210
+ - lib/3scale_toolbox/commands/proxy_config_command/list_command.rb
211
+ - lib/3scale_toolbox/commands/proxy_config_command/promote_command.rb
212
+ - lib/3scale_toolbox/commands/proxy_config_command/show_command.rb
194
213
  - lib/3scale_toolbox/commands/remote_command.rb
195
214
  - lib/3scale_toolbox/commands/remote_command/remote_add.rb
196
215
  - lib/3scale_toolbox/commands/remote_command/remote_list.rb
197
216
  - lib/3scale_toolbox/commands/remote_command/remote_remove.rb
198
217
  - lib/3scale_toolbox/commands/remote_command/remote_rename.rb
218
+ - lib/3scale_toolbox/commands/service_command.rb
219
+ - lib/3scale_toolbox/commands/service_command/apply_command.rb
220
+ - lib/3scale_toolbox/commands/service_command/create_command.rb
221
+ - lib/3scale_toolbox/commands/service_command/delete_command.rb
222
+ - lib/3scale_toolbox/commands/service_command/list_command.rb
223
+ - lib/3scale_toolbox/commands/service_command/show_command.rb
199
224
  - lib/3scale_toolbox/commands/update_command.rb
200
225
  - lib/3scale_toolbox/commands/update_command/update_service.rb
201
226
  - lib/3scale_toolbox/configuration.rb
202
227
  - lib/3scale_toolbox/entities.rb
228
+ - lib/3scale_toolbox/entities/account.rb
229
+ - lib/3scale_toolbox/entities/activedocs.rb
230
+ - lib/3scale_toolbox/entities/application.rb
203
231
  - lib/3scale_toolbox/entities/application_plan.rb
232
+ - lib/3scale_toolbox/entities/base_entity.rb
204
233
  - lib/3scale_toolbox/entities/method.rb
205
234
  - lib/3scale_toolbox/entities/metric.rb
235
+ - lib/3scale_toolbox/entities/proxy_config.rb
206
236
  - lib/3scale_toolbox/entities/service.rb
207
237
  - lib/3scale_toolbox/error.rb
208
238
  - lib/3scale_toolbox/helper.rb
@@ -212,6 +242,7 @@ files:
212
242
  - lib/3scale_toolbox/swagger.rb
213
243
  - lib/3scale_toolbox/swagger/swagger.rb
214
244
  - lib/3scale_toolbox/tasks.rb
245
+ - lib/3scale_toolbox/tasks/bump_proxy_version_task.rb
215
246
  - lib/3scale_toolbox/tasks/copy_activedocs_task.rb
216
247
  - lib/3scale_toolbox/tasks/copy_app_plans_task.rb
217
248
  - lib/3scale_toolbox/tasks/copy_limits_task.rb
@@ -221,11 +252,11 @@ files:
221
252
  - lib/3scale_toolbox/tasks/copy_policies_task.rb
222
253
  - lib/3scale_toolbox/tasks/copy_pricingrules_task.rb
223
254
  - lib/3scale_toolbox/tasks/copy_service_proxy_task.rb
255
+ - lib/3scale_toolbox/tasks/copy_service_settings_task.rb
224
256
  - lib/3scale_toolbox/tasks/copy_task.rb
225
257
  - lib/3scale_toolbox/tasks/delete_activedocs_task.rb
226
258
  - lib/3scale_toolbox/tasks/destroy_mapping_rules_task.rb
227
259
  - lib/3scale_toolbox/tasks/helper_task.rb
228
- - lib/3scale_toolbox/tasks/update_service_settings_task.rb
229
260
  - lib/3scale_toolbox/version.rb
230
261
  - resources/swagger_meta_schema.json
231
262
  homepage: https://github.com/3scale/3scale_toolbox
@@ -1,49 +0,0 @@
1
- module ThreeScaleToolbox
2
- module Tasks
3
- class UpdateServiceSettingsTask
4
- attr_reader :source, :target, :target_system_name
5
-
6
- def initialize(source:, target:, target_name:)
7
- @source = source
8
- @target = target
9
- @target_system_name = target_name
10
- end
11
-
12
- def call
13
- source_obj = source.attrs
14
- svc_obj = update_service target_service_params(source_obj)
15
- if (errors = svc_obj['errors'])
16
- raise ThreeScaleToolbox::Error, "Service has not been saved. Errors: #{errors}" \
17
- end
18
-
19
- puts "updated service settings for service id #{source.id}..."
20
- end
21
-
22
- private
23
-
24
- def update_service(service)
25
- svc_obj = target.update service
26
-
27
- # Source and target remotes might not allow same set of deployment options
28
- # Invalid deployment option check
29
- # use default deployment_option
30
- if (errors = svc_obj['errors']) &&
31
- ThreeScaleToolbox::Helper.service_invalid_deployment_option?(errors)
32
- service.delete('deployment_option')
33
- svc_obj = target.update service
34
- end
35
-
36
- svc_obj
37
- end
38
-
39
- # system name only included when specified from options
40
- def target_service_params(source)
41
- target_svc_obj = ThreeScaleToolbox::Helper.filter_params(Entities::Service::VALID_PARAMS, source)
42
- target_svc_obj.delete('system_name')
43
- target_svc_obj.tap do |hash|
44
- hash['system_name'] = target_system_name unless target_system_name.nil?
45
- end
46
- end
47
- end
48
- end
49
- end