fog-azure-rm 0.2.7 → 0.3.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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/fog-azure-rm.gemspec +8 -8
  4. data/lib/fog/azurerm.rb +5 -0
  5. data/lib/fog/azurerm/async_response.rb +36 -0
  6. data/lib/fog/azurerm/compute.rb +15 -1
  7. data/lib/fog/azurerm/constants.rb +6 -0
  8. data/lib/fog/azurerm/docs/compute.md +145 -2
  9. data/lib/fog/azurerm/models/compute/creation_data.rb +21 -0
  10. data/lib/fog/azurerm/models/compute/disk_create_option.rb +16 -0
  11. data/lib/fog/azurerm/models/compute/encryption_settings.rb +29 -0
  12. data/lib/fog/azurerm/models/compute/image_disk_reference.rb +15 -0
  13. data/lib/fog/azurerm/models/compute/managed_disk.rb +77 -0
  14. data/lib/fog/azurerm/models/compute/managed_disks.rb +43 -0
  15. data/lib/fog/azurerm/models/compute/operation_status_response.rb +18 -0
  16. data/lib/fog/azurerm/models/compute/server.rb +12 -7
  17. data/lib/fog/azurerm/models/compute/servers.rb +6 -0
  18. data/lib/fog/azurerm/models/traffic_manager/traffic_manager_profile.rb +1 -0
  19. data/lib/fog/azurerm/requests/compute/check_managed_disk_exists.rb +33 -0
  20. data/lib/fog/azurerm/requests/compute/create_or_update_managed_disk.rb +121 -0
  21. data/lib/fog/azurerm/requests/compute/create_virtual_machine.rb +24 -11
  22. data/lib/fog/azurerm/requests/compute/delete_managed_disk.rb +29 -0
  23. data/lib/fog/azurerm/requests/compute/delete_virtual_machine.rb +1 -1
  24. data/lib/fog/azurerm/requests/compute/get_managed_disk.rb +64 -0
  25. data/lib/fog/azurerm/requests/compute/grant_access_to_managed_disk.rb +30 -0
  26. data/lib/fog/azurerm/requests/compute/list_managed_disks_by_rg.rb +66 -0
  27. data/lib/fog/azurerm/requests/compute/list_managed_disks_in_subscription.rb +66 -0
  28. data/lib/fog/azurerm/requests/compute/revoke_access_to_managed_disk.rb +33 -0
  29. data/lib/fog/azurerm/requests/dns/check_zone_exists.rb +5 -1
  30. data/lib/fog/azurerm/requests/network/check_net_sec_rule_exists.rb +1 -1
  31. data/lib/fog/azurerm/requests/network/check_subnet_exists.rb +1 -1
  32. data/lib/fog/azurerm/utilities/general.rb +13 -10
  33. data/lib/fog/azurerm/version.rb +1 -1
  34. data/test/api_stub.rb +2 -0
  35. data/test/api_stub/models/compute/managed_disk.rb +59 -0
  36. data/test/api_stub/requests/compute/managed_disk.rb +102 -0
  37. data/test/api_stub/requests/compute/virtual_machine.rb +6 -6
  38. data/test/integration/Virtual_network_gateway_connection.rb +0 -6
  39. data/test/integration/application_gateway.rb +1 -1
  40. data/test/integration/credentials/azure.yml +1 -1
  41. data/test/integration/managed_disk.rb +113 -0
  42. data/test/integration/server.rb +39 -1
  43. data/test/integration/traffic_manager.rb +2 -2
  44. data/test/models/compute/test_managed_disk.rb +61 -0
  45. data/test/models/compute/test_managed_disks.rb +68 -0
  46. data/test/models/compute/test_server.rb +7 -1
  47. data/test/models/compute/test_servers.rb +1 -0
  48. data/test/requests/compute/test_check_managed_disk_exists.rb +31 -0
  49. data/test/requests/compute/test_create_or_update_managed_disk.rb +38 -0
  50. data/test/requests/compute/test_create_virtual_machine.rb +27 -0
  51. data/test/requests/compute/test_delete_managed_disk.rb +23 -0
  52. data/test/requests/compute/test_get_managed_disk.rb +24 -0
  53. data/test/requests/compute/test_grant_access_to_managed_disk.rb +26 -0
  54. data/test/requests/compute/test_list_managed_disks_by_rg.rb +24 -0
  55. data/test/requests/compute/test_list_managed_disks_in_subscription.rb +24 -0
  56. data/test/requests/compute/test_revoke_access_to_managed_disk.rb +24 -0
  57. data/test/test_helper.rb +15 -1
  58. metadata +47 -18
@@ -0,0 +1,30 @@
1
+ module Fog
2
+ module Compute
3
+ class AzureRM
4
+ # Real class for Compute Request
5
+ class Real
6
+ def grant_access_to_managed_disk(resource_group_name, disk_name, access_type, duration_in_sec)
7
+ msg = "Granting access to Managed Disk: #{disk_name}"
8
+ Fog::Logger.debug msg
9
+ access_data = Azure::ARM::Compute::Models::GrantAccessData.new
10
+ access_data.access = access_type
11
+ access_data.duration_in_seconds = duration_in_sec
12
+ begin
13
+ access_uri = @compute_mgmt_client.disks.grant_access(resource_group_name, disk_name, access_data)
14
+ rescue MsRestAzure::AzureOperationError => e
15
+ raise_azure_exception(e, msg)
16
+ end
17
+ Fog::Logger.debug "Access granted to managed disk: #{disk_name} successfully."
18
+ access_uri.access_sas
19
+ end
20
+ end
21
+
22
+ # Mock class for Compute Request
23
+ class Mock
24
+ def grant_access_to_managed_disk(*)
25
+ 'ACCESS URI'
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,66 @@
1
+ module Fog
2
+ module Compute
3
+ class AzureRM
4
+ # Real class for Compute Request
5
+ class Real
6
+ def list_managed_disks_by_rg(resource_group_name)
7
+ msg = "Listing all Managed Disks in resource group: #{resource_group_name}"
8
+ Fog::Logger.debug msg
9
+ begin
10
+ managed_disks = @compute_mgmt_client.disks.list_by_resource_group(resource_group_name)
11
+ rescue MsRestAzure::AzureOperationError => e
12
+ raise_azure_exception(e, msg)
13
+ end
14
+ Fog::Logger.debug 'Managed Disks listed successfully.'
15
+ managed_disks
16
+ end
17
+ end
18
+
19
+ # Mock class for Compute Request
20
+ class Mock
21
+ def list_managed_disks_by_rg(*)
22
+ disks = [
23
+ {
24
+ 'accountType' => 'Standard_LRS',
25
+ 'properties' => {
26
+ 'osType' => 'Windows',
27
+ 'creationData' => {
28
+ 'createOption' => 'Empty'
29
+ },
30
+ 'diskSizeGB' => 10,
31
+ 'encryptionSettings' => {
32
+ 'enabled' => true,
33
+ 'diskEncryptionKey' => {
34
+ 'sourceVault' => {
35
+ 'id' => '/subscriptions/{subscriptionId}/resourceGroups/myResourceGroup/providers/Microsoft.KeyVault/vaults/myVMVault'
36
+ },
37
+ 'secretUrl' => 'https://myvmvault.vault-int.azure-int.net/secrets/{secret}'
38
+ },
39
+ 'keyEncryptionKey' => {
40
+ 'sourceVault' => {
41
+ 'id' => '/subscriptions/{subscriptionId}/resourceGroups/myResourceGroup/providers/Microsoft.KeyVault/vaults/myVMVault'
42
+ },
43
+ 'keyUrl' => 'https://myvmvault.vault-int.azure-int.net/keys/{key}'
44
+ }
45
+ },
46
+ 'timeCreated' => '2016-12-28T02:46:21.3322041+00:00',
47
+ 'provisioningState' => 'Succeeded',
48
+ 'diskState' => 'Unattached'
49
+ },
50
+ 'type' => 'Microsoft.Compute/disks',
51
+ 'location' => 'westus',
52
+ 'tags' => {
53
+ 'department' => 'Development',
54
+ 'project' => 'ManagedDisks'
55
+ },
56
+ 'id' => '/subscriptions/{subscriptionId}/resourceGroups/myResourceGroup/providers/Microsoft.Compute/disks/myManagedDisk1',
57
+ 'name' => 'myManagedDisk1'
58
+ }
59
+ ]
60
+ disk_mapper = Azure::ARM::Compute::Models::DiskList.mapper
61
+ @compute_mgmt_client.deserialize(disk_mapper, disks, 'result.body').value
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,66 @@
1
+ module Fog
2
+ module Compute
3
+ class AzureRM
4
+ # Real class for Compute Request
5
+ class Real
6
+ def list_managed_disks_in_subscription
7
+ msg = 'Listing all Managed Disks'
8
+ Fog::Logger.debug msg
9
+ begin
10
+ managed_disks = @compute_mgmt_client.disks.list
11
+ rescue MsRestAzure::AzureOperationError => e
12
+ raise_azure_exception(e, msg)
13
+ end
14
+ Fog::Logger.debug 'Managed Disks listed successfully.'
15
+ managed_disks
16
+ end
17
+ end
18
+
19
+ # Mock class for Compute Request
20
+ class Mock
21
+ def list_managed_disks_in_subscription
22
+ disks = [
23
+ {
24
+ 'accountType' => 'Standard_LRS',
25
+ 'properties' => {
26
+ 'osType' => 'Windows',
27
+ 'creationData' => {
28
+ 'createOption' => 'Empty'
29
+ },
30
+ 'diskSizeGB' => 10,
31
+ 'encryptionSettings' => {
32
+ 'enabled' => true,
33
+ 'diskEncryptionKey' => {
34
+ 'sourceVault' => {
35
+ 'id' => '/subscriptions/{subscriptionId}/resourceGroups/myResourceGroup/providers/Microsoft.KeyVault/vaults/myVMVault'
36
+ },
37
+ 'secretUrl' => 'https://myvmvault.vault-int.azure-int.net/secrets/{secret}'
38
+ },
39
+ 'keyEncryptionKey' => {
40
+ 'sourceVault' => {
41
+ 'id' => '/subscriptions/{subscriptionId}/resourceGroups/myResourceGroup/providers/Microsoft.KeyVault/vaults/myVMVault'
42
+ },
43
+ 'keyUrl' => 'https://myvmvault.vault-int.azure-int.net/keys/{key}'
44
+ }
45
+ },
46
+ 'timeCreated' => '2016-12-28T02:46:21.3322041+00:00',
47
+ 'provisioningState' => 'Succeeded',
48
+ 'diskState' => 'Unattached'
49
+ },
50
+ 'type' => 'Microsoft.Compute/disks',
51
+ 'location' => 'westus',
52
+ 'tags' => {
53
+ 'department' => 'Development',
54
+ 'project' => 'ManagedDisks'
55
+ },
56
+ 'id' => '/subscriptions/{subscriptionId}/resourceGroups/myResourceGroup/providers/Microsoft.Compute/disks/myManagedDisk1',
57
+ 'name' => 'myManagedDisk1'
58
+ }
59
+ ]
60
+ disk_mapper = Azure::ARM::Compute::Models::DiskList.mapper
61
+ @compute_mgmt_client.deserialize(disk_mapper, disks, 'result.body').value
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,33 @@
1
+ module Fog
2
+ module Compute
3
+ class AzureRM
4
+ # Real class for Compute Request
5
+ class Real
6
+ def revoke_access_to_managed_disk(resource_group_name, disk_name)
7
+ msg = "Revoking access to Managed Disk: #{disk_name}"
8
+ Fog::Logger.debug msg
9
+ begin
10
+ response = @compute_mgmt_client.disks.revoke_access(resource_group_name, disk_name)
11
+ rescue MsRestAzure::AzureOperationError => e
12
+ raise_azure_exception(e, msg)
13
+ end
14
+ Fog::Logger.debug "Access revoked to managed disk: #{disk_name} successfully."
15
+ response
16
+ end
17
+ end
18
+
19
+ # Mock class for Compute Request
20
+ class Mock
21
+ def revoke_access_to_managed_disk(*)
22
+ response = {
23
+ 'name' => 'revoke',
24
+ 'status' => 200,
25
+ 'error' => 'Error Details'
26
+ }
27
+ response_mapper = Azure::ARM::Compute::Models::OperationStatusResponse.mapper
28
+ @compute_mgmt_client.deserialize(response_mapper, response, 'result.body')
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -9,7 +9,11 @@ module Fog
9
9
  begin
10
10
  zone = @dns_client.zones.get(resource_group, name)
11
11
  rescue MsRestAzure::AzureOperationError => e
12
- raise_azure_exception(e, msg)
12
+ if !e.body['error'].nil? && e.body['error']['code'] == ERROR_CODE_RESOURCE_NOT_FOUND
13
+ zone = nil
14
+ else
15
+ raise_azure_exception(e, msg)
16
+ end
13
17
  rescue => e
14
18
  Fog::Logger.debug e[:error][:code]
15
19
  end
@@ -11,7 +11,7 @@ module Fog
11
11
  Fog::Logger.debug "Network Security Rule #{security_rule_name} exists."
12
12
  true
13
13
  rescue MsRestAzure::AzureOperationError => e
14
- if e.body['error']['code'] == 'ResourceNotFound'
14
+ if !e.body['error'].nil? && (e.body['error']['code'] == ERROR_CODE_RESOURCE_NOT_FOUND || e.body['error']['code'] == ERROR_CODE_NOT_FOUND)
15
15
  Fog::Logger.debug "Network Security Rule #{security_rule_name} doesn't exist."
16
16
  false
17
17
  else
@@ -11,7 +11,7 @@ module Fog
11
11
  Fog::Logger.debug "Subnet #{subnet_name} exists."
12
12
  true
13
13
  rescue MsRestAzure::AzureOperationError => e
14
- if e.body['error']['code'] == 'ResourceNotFound'
14
+ if !e.body['error'].nil? && (e.body['error']['code'] == ERROR_CODE_RESOURCE_NOT_FOUND || e.body['error']['code'] == ERROR_CODE_NOT_FOUND)
15
15
  Fog::Logger.debug "Subnet #{subnet_name} doesn't exist."
16
16
  false
17
17
  else
@@ -47,22 +47,25 @@ def get_record_type(type)
47
47
  end
48
48
 
49
49
  def raise_azure_exception(exception, msg)
50
- message = if exception.respond_to? 'body'
51
- "Exception in #{msg} #{exception.body['error']['message'] unless exception.body['error']['message'].nil?} Type: #{exception.class}\n#{exception.backtrace.join("\n")}"
52
- else
53
- "#{exception.inspect}\n#{exception.backtrace.join("\n")}"
54
- end
50
+ if exception.respond_to? 'body'
51
+ message = if exception.body['error'].nil?
52
+ exception.body['message']
53
+ else
54
+ exception.body['error']['message']
55
+ end
56
+ exception_message = "Exception in #{msg} #{message} Type: #{exception.class}\n#{exception.backtrace.join("\n")}"
57
+ else
58
+ "#{exception.inspect}\n#{exception.backtrace.join("\n")}"
59
+ end
60
+
55
61
  Fog::Logger.debug exception.backtrace
56
- raise message
62
+ raise exception_message
57
63
  end
58
64
 
59
65
  # Make sure if input_params(Hash) contains all keys present in required_params(Array)
60
66
  def validate_params(required_params, input_params)
61
67
  missing_params = required_params.select { |param| param unless input_params.key?(param) }
62
-
63
- if missing_params.any?
64
- raise(ArgumentError, "Missing Parameters: #{missing_params.join(', ')} required for this operation")
65
- end
68
+ raise(ArgumentError, "Missing Parameters: #{missing_params.join(', ')} required for this operation") if missing_params.any?
66
69
  end
67
70
 
68
71
  def get_resource_from_resource_id(resource_id, position)
@@ -1,5 +1,5 @@
1
1
  module Fog
2
2
  module AzureRM
3
- VERSION = '0.2.7'.freeze
3
+ VERSION = '0.3.0'.freeze
4
4
  end
5
5
  end
@@ -5,6 +5,7 @@ module ApiStub
5
5
  autoload :Server, File.expand_path('api_stub/models/compute/server', __dir__)
6
6
  autoload :AvailabilitySet, File.expand_path('api_stub/models/compute/availability_set', __dir__)
7
7
  autoload :VirtualMachineExtension, File.expand_path('api_stub/models/compute/virtual_machine_extension', __dir__)
8
+ autoload :ManagedDisk, File.expand_path('api_stub/models/compute/managed_disk', __dir__)
8
9
  end
9
10
 
10
11
  module Resources
@@ -67,6 +68,7 @@ module ApiStub
67
68
  autoload :AvailabilitySet, File.expand_path('api_stub/requests/compute/availability_set', __dir__)
68
69
  autoload :VirtualMachine, File.expand_path('api_stub/requests/compute/virtual_machine', __dir__)
69
70
  autoload :VirtualMachineExtension, File.expand_path('api_stub/requests/compute/virtual_machine_extension', __dir__)
71
+ autoload :ManagedDisk, File.expand_path('api_stub/requests/compute/managed_disk', __dir__)
70
72
  end
71
73
 
72
74
  module Resources
@@ -0,0 +1,59 @@
1
+ module ApiStub
2
+ module Models
3
+ module Compute
4
+ # Mock class for ManagedDisk Model
5
+ class ManagedDisk
6
+ def self.create_managed_disk_response(sdk_compute_client)
7
+ disk = {
8
+ 'accountType' => 'Standard_LRS',
9
+ 'properties' => {
10
+ 'osType' => 'Windows',
11
+ 'creationData' => {
12
+ 'createOption' => 'Empty'
13
+ },
14
+ 'diskSizeGB' => 10,
15
+ 'encryptionSettings' => {
16
+ 'enabled' => true,
17
+ 'diskEncryptionKey' => {
18
+ 'sourceVault' => {
19
+ 'id' => '/subscriptions/{subscriptionId}/resourceGroups/myResourceGroup/providers/Microsoft.KeyVault/vaults/myVMVault'
20
+ },
21
+ 'secretUrl' => 'https://myvmvault.vault-int.azure-int.net/secrets/{secret}'
22
+ },
23
+ 'keyEncryptionKey' => {
24
+ 'sourceVault' => {
25
+ 'id' => '/subscriptions/{subscriptionId}/resourceGroups/myResourceGroup/providers/Microsoft.KeyVault/vaults/myVMVault'
26
+ },
27
+ 'keyUrl' => 'https://myvmvault.vault-int.azure-int.net/keys/{key}'
28
+ }
29
+ },
30
+ 'timeCreated' => '2016-12-28T02:46:21.3322041+00:00',
31
+ 'provisioningState' => 'Succeeded',
32
+ 'diskState' => 'Unattached'
33
+ },
34
+ 'type' => 'Microsoft.Compute/disks',
35
+ 'location' => 'westus',
36
+ 'tags' => {
37
+ 'department' => 'Development',
38
+ 'project' => 'ManagedDisks'
39
+ },
40
+ 'id' => '/subscriptions/{subscriptionId}/resourceGroups/myResourceGroup/providers/Microsoft.Compute/disks/myManagedDisk1',
41
+ 'name' => 'myManagedDisk1'
42
+ }
43
+ result_mapper = Azure::ARM::Compute::Models::Disk.mapper
44
+ sdk_compute_client.deserialize(result_mapper, disk, 'result.body')
45
+ end
46
+
47
+ def self.operation_status_response(sdk_compute_client)
48
+ response = {
49
+ 'name' => 'xxxx-xxxxx-xxxx',
50
+ 'status' => 'success',
51
+ 'error' => 'ERROR'
52
+ }
53
+ response_mapper = Azure::ARM::Compute::Models::OperationStatusResponse.mapper
54
+ sdk_compute_client.deserialize(response_mapper, response, 'result.body')
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,102 @@
1
+ module ApiStub
2
+ module Requests
3
+ module Compute
4
+ # Mock class for Managed Disk Requests
5
+ class ManagedDisk
6
+ def self.create_or_update_managed_disk_response(compute_client)
7
+ body = '{
8
+ "accountType": "Standard_LRS",
9
+ "properties": {
10
+ "osType": "Windows",
11
+ "creationData": {
12
+ "createOption": "Empty"
13
+ },
14
+ "diskSizeGB": 10,
15
+ "encryptionSettings": {
16
+ "enabled": true,
17
+ "diskEncryptionKey": {
18
+ "sourceVault": {
19
+ "id": "/subscriptions/{subscriptionId}/resourceGroups/myResourceGroup/providers/Microsoft.KeyVault/vaults/myVMVault"
20
+ },
21
+ "secretUrl": "https://myvmvault.vault-int.azure-int.net/secrets/{secret}"
22
+ },
23
+ "keyEncryptionKey": {
24
+ "sourceVault": {
25
+ "id": "/subscriptions/{subscriptionId}/resourceGroups/myResourceGroup/providers/Microsoft.KeyVault/vaults/myVMVault"
26
+ },
27
+ "keyUrl": "https://myvmvault.vault-int.azure-int.net/keys/{key}"
28
+ }
29
+ },
30
+ "timeCreated": "2016-12-28T02:46:21.3322041+00:00",
31
+ "provisioningState": "Succeeded",
32
+ "diskState": "Unattached"
33
+ },
34
+ "type": "Microsoft.Compute/disks",
35
+ "location": "westus",
36
+ "tags": {
37
+ "department": "Development",
38
+ "project": "ManagedDisks"
39
+ },
40
+ "id": "/subscriptions/{subscriptionId}/resourceGroups/myResourceGroup/providers/Microsoft.Compute/disks/myManagedDisk1",
41
+ "name": "myManagedDisk1"
42
+ }'
43
+ disk_mapper = Azure::ARM::Compute::Models::Disk.mapper
44
+ compute_client.deserialize(disk_mapper, Fog::JSON.decode(body), 'result.body')
45
+ end
46
+
47
+ def self.get_managed_disk_response(sdk_compute_client)
48
+ body = '{
49
+ "value": [ {
50
+ "accountType": "Standard_LRS",
51
+ "properties": {
52
+ "osType": "Windows",
53
+ "creationData": {
54
+ "createOption": "Empty"
55
+ },
56
+ "diskSizeGB": 10,
57
+ "encryptionSettings": {
58
+ "enabled": true,
59
+ "diskEncryptionKey": {
60
+ "sourceVault": {
61
+ "id": "/subscriptions/{subscriptionId}/resourceGroups/myResourceGroup/providers/Microsoft.KeyVault/vaults/myVMVault"
62
+ },
63
+ "secretUrl": "https://myvmvault.vault-int.azure-int.net/secrets/{secret}"
64
+ },
65
+ "keyEncryptionKey": {
66
+ "sourceVault": {
67
+ "id": "/subscriptions/{subscriptionId}/resourceGroups/myResourceGroup/providers/Microsoft.KeyVault/vaults/myVMVault"
68
+ },
69
+ "keyUrl": "https://myvmvault.vault-int.azure-int.net/keys/{key}"
70
+ }
71
+ },
72
+ "timeCreated": "2016-12-28T02:46:21.3322041+00:00",
73
+ "provisioningState": "Succeeded",
74
+ "diskState": "Unattached"
75
+ },
76
+ "type": "Microsoft.Compute/disks",
77
+ "location": "westus",
78
+ "tags": {
79
+ "department": "Development",
80
+ "project": "ManagedDisks"
81
+ },
82
+ "id": "/subscriptions/{subscriptionId}/resourceGroups/myResourceGroup/providers/Microsoft.Compute/disks/myManagedDisk1",
83
+ "name": "myManagedDisk1"
84
+ } ]
85
+ }'
86
+ disk_mapper = Azure::ARM::Compute::Models::Disk.mapper
87
+ sdk_compute_client.deserialize(disk_mapper, Fog::JSON.decode(body), 'result.body')
88
+ end
89
+
90
+ def self.operation_status_response(sdk_compute_client)
91
+ response = {
92
+ 'name' => 'xxxx-xxxxx-xxxx',
93
+ 'status' => 'success',
94
+ 'error' => 'ERROR'
95
+ }
96
+ response_mapper = Azure::ARM::Compute::Models::OperationStatusResponse.mapper
97
+ sdk_compute_client.deserialize(response_mapper, response, 'result.body')
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end