morpheus-cli 5.3.3 → 5.4.2

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 (209) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/account_users_interface.rb +68 -0
  4. data/lib/morpheus/api/api_client.rb +75 -8
  5. data/lib/morpheus/api/audit_interface.rb +9 -0
  6. data/lib/morpheus/api/clouds_interface.rb +4 -11
  7. data/lib/morpheus/api/health_interface.rb +37 -3
  8. data/lib/morpheus/api/instances_interface.rb +21 -0
  9. data/lib/morpheus/api/load_balancer_monitors_interface.rb +9 -0
  10. data/lib/morpheus/api/load_balancer_profiles_interface.rb +9 -0
  11. data/lib/morpheus/api/load_balancer_virtual_servers_interface.rb +9 -0
  12. data/lib/morpheus/api/load_balancers_interface.rb +5 -0
  13. data/lib/morpheus/api/network_dhcp_relays_interface.rb +36 -0
  14. data/lib/morpheus/api/network_dhcp_servers_interface.rb +36 -0
  15. data/lib/morpheus/api/network_edge_clusters_interface.rb +26 -0
  16. data/lib/morpheus/api/network_routers_interface.rb +30 -0
  17. data/lib/morpheus/api/network_servers_interface.rb +98 -0
  18. data/lib/morpheus/api/network_static_routes_interface.rb +36 -0
  19. data/lib/morpheus/api/read_interface.rb +4 -3
  20. data/lib/morpheus/api/rest_interface.rb +5 -4
  21. data/lib/morpheus/api/roles_interface.rb +7 -0
  22. data/lib/morpheus/api/secondary_read_interface.rb +1 -1
  23. data/lib/morpheus/api/secondary_rest_interface.rb +19 -19
  24. data/lib/morpheus/api/storage_server_types_interface.rb +14 -0
  25. data/lib/morpheus/api/storage_servers_interface.rb +9 -0
  26. data/lib/morpheus/api/storage_volume_types_interface.rb +9 -0
  27. data/lib/morpheus/api/storage_volumes_interface.rb +9 -0
  28. data/lib/morpheus/api/users_interface.rb +16 -63
  29. data/lib/morpheus/api/virtual_servers_interface.rb +9 -0
  30. data/lib/morpheus/cli/cli_command.rb +262 -14
  31. data/lib/morpheus/cli/cli_registry.rb +2 -1
  32. data/lib/morpheus/cli/{access_token_command.rb → commands/access_token_command.rb} +1 -1
  33. data/lib/morpheus/cli/{account_groups_command.rb → commands/account_groups_command.rb} +0 -8
  34. data/lib/morpheus/cli/{activity_command.rb → commands/activity_command.rb} +0 -0
  35. data/lib/morpheus/cli/commands/{standard/alias_command.rb → alias_command.rb} +1 -4
  36. data/lib/morpheus/cli/{appliance_settings_command.rb → commands/appliance_settings_command.rb} +0 -0
  37. data/lib/morpheus/cli/{approvals_command.rb → commands/approvals_command.rb} +0 -0
  38. data/lib/morpheus/cli/{apps.rb → commands/apps.rb} +14 -87
  39. data/lib/morpheus/cli/{archives_command.rb → commands/archives_command.rb} +0 -6
  40. data/lib/morpheus/cli/commands/audit.rb +188 -0
  41. data/lib/morpheus/cli/{backup_jobs_command.rb → commands/backup_jobs_command.rb} +0 -0
  42. data/lib/morpheus/cli/{backup_settings_command.rb → commands/backup_settings_command.rb} +0 -0
  43. data/lib/morpheus/cli/{backups_command.rb → commands/backups_command.rb} +0 -0
  44. data/lib/morpheus/cli/commands/{standard/benchmark_command.rb → benchmark_command.rb} +0 -3
  45. data/lib/morpheus/cli/{blueprints_command.rb → commands/blueprints_command.rb} +1 -1
  46. data/lib/morpheus/cli/{boot_scripts_command.rb → commands/boot_scripts_command.rb} +0 -3
  47. data/lib/morpheus/cli/{budgets_command.rb → commands/budgets_command.rb} +0 -0
  48. data/lib/morpheus/cli/commands/{standard/cat_command.rb → cat_command.rb} +0 -0
  49. data/lib/morpheus/cli/{catalog_item_types_command.rb → commands/catalog_item_types_command.rb} +0 -0
  50. data/lib/morpheus/cli/{certificates_command.rb → commands/certificates_command.rb} +0 -0
  51. data/lib/morpheus/cli/commands/change_password_command.rb +132 -0
  52. data/lib/morpheus/cli/{cloud_datastores_command.rb → commands/cloud_datastores_command.rb} +0 -4
  53. data/lib/morpheus/cli/{cloud_folders_command.rb → commands/cloud_folders_command.rb} +0 -4
  54. data/lib/morpheus/cli/{cloud_resource_pools_command.rb → commands/cloud_resource_pools_command.rb} +1 -5
  55. data/lib/morpheus/cli/{clouds.rb → commands/clouds.rb} +22 -47
  56. data/lib/morpheus/cli/{clusters.rb → commands/clusters.rb} +38 -19
  57. data/lib/morpheus/cli/commands/{standard/coloring_command.rb → coloring_command.rb} +0 -2
  58. data/lib/morpheus/cli/{containers_command.rb → commands/containers_command.rb} +0 -7
  59. data/lib/morpheus/cli/commands/{standard/curl_command.rb → curl_command.rb} +0 -3
  60. data/lib/morpheus/cli/{cypher_command.rb → commands/cypher_command.rb} +0 -1
  61. data/lib/morpheus/cli/{dashboard_command.rb → commands/dashboard_command.rb} +0 -2
  62. data/lib/morpheus/cli/commands/{standard/debug_command.rb → debug_command.rb} +0 -1
  63. data/lib/morpheus/cli/{deploy.rb → commands/deploy.rb} +0 -1
  64. data/lib/morpheus/cli/{deployments.rb → commands/deployments.rb} +0 -0
  65. data/lib/morpheus/cli/{deploys.rb → commands/deploys.rb} +0 -1
  66. data/lib/morpheus/cli/{doc.rb → commands/doc.rb} +1 -1
  67. data/lib/morpheus/cli/commands/{standard/echo_command.rb → echo_command.rb} +0 -2
  68. data/lib/morpheus/cli/commands/{standard/edit_profile_command.rb → edit_profile_command.rb} +15 -4
  69. data/lib/morpheus/cli/commands/{standard/edit_rc_command.rb → edit_rc_command.rb} +19 -3
  70. data/lib/morpheus/cli/{environments_command.rb → commands/environments_command.rb} +0 -5
  71. data/lib/morpheus/cli/{execute_schedules_command.rb → commands/execute_schedules_command.rb} +0 -0
  72. data/lib/morpheus/cli/{execution_request_command.rb → commands/execution_request_command.rb} +0 -2
  73. data/lib/morpheus/cli/commands/{standard/exit_command.rb → exit_command.rb} +0 -2
  74. data/lib/morpheus/cli/{file_copy_request_command.rb → commands/file_copy_request_command.rb} +0 -4
  75. data/lib/morpheus/cli/{forgot_password.rb → commands/forgot_password.rb} +0 -0
  76. data/lib/morpheus/cli/commands/{standard/get_prompt_command.rb → get_prompt_command.rb} +0 -3
  77. data/lib/morpheus/cli/{groups.rb → commands/groups.rb} +0 -7
  78. data/lib/morpheus/cli/{guidance_command.rb → commands/guidance_command.rb} +1 -1
  79. data/lib/morpheus/cli/{health_command.rb → commands/health_command.rb} +104 -19
  80. data/lib/morpheus/cli/commands/{standard/history_command.rb → history_command.rb} +0 -3
  81. data/lib/morpheus/cli/{hosts.rb → commands/hosts.rb} +15 -25
  82. data/lib/morpheus/cli/{image_builder_command.rb → commands/image_builder_command.rb} +6 -16
  83. data/lib/morpheus/cli/{instance_types.rb → commands/instance_types.rb} +0 -3
  84. data/lib/morpheus/cli/{instances.rb → commands/instances.rb} +220 -13
  85. data/lib/morpheus/cli/{integrations_command.rb → commands/integrations_command.rb} +1 -12
  86. data/lib/morpheus/cli/{invoices_command.rb → commands/invoices_command.rb} +43 -39
  87. data/lib/morpheus/cli/{jobs_command.rb → commands/jobs_command.rb} +0 -0
  88. data/lib/morpheus/cli/{key_pairs.rb → commands/key_pairs.rb} +0 -6
  89. data/lib/morpheus/cli/{library_cluster_layouts_command.rb → commands/library_cluster_layouts_command.rb} +0 -4
  90. data/lib/morpheus/cli/{library_container_scripts_command.rb → commands/library_container_scripts_command.rb} +0 -0
  91. data/lib/morpheus/cli/{library_container_templates_command.rb → commands/library_container_templates_command.rb} +0 -1
  92. data/lib/morpheus/cli/{library_container_types_command.rb → commands/library_container_types_command.rb} +0 -4
  93. data/lib/morpheus/cli/{library_instance_types_command.rb → commands/library_instance_types_command.rb} +0 -4
  94. data/lib/morpheus/cli/{library_layouts_command.rb → commands/library_layouts_command.rb} +0 -4
  95. data/lib/morpheus/cli/{library_option_lists_command.rb → commands/library_option_lists_command.rb} +3 -7
  96. data/lib/morpheus/cli/{library_option_types_command.rb → commands/library_option_types_command.rb} +0 -4
  97. data/lib/morpheus/cli/{library_spec_templates_command.rb → commands/library_spec_templates_command.rb} +0 -1
  98. data/lib/morpheus/cli/{library_upgrades_command.rb → commands/library_upgrades_command.rb} +0 -4
  99. data/lib/morpheus/cli/{license.rb → commands/license.rb} +0 -3
  100. data/lib/morpheus/cli/commands/load_balancer_monitors.rb +70 -0
  101. data/lib/morpheus/cli/commands/load_balancer_pools.rb +90 -0
  102. data/lib/morpheus/cli/commands/load_balancer_profiles.rb +64 -0
  103. data/lib/morpheus/cli/{load_balancer_types.rb → commands/load_balancer_types.rb} +9 -8
  104. data/lib/morpheus/cli/commands/load_balancer_virtual_servers.rb +147 -0
  105. data/lib/morpheus/cli/commands/load_balancers.rb +192 -0
  106. data/lib/morpheus/cli/commands/{standard/log_level_command.rb → log_level_command.rb} +0 -3
  107. data/lib/morpheus/cli/{log_settings_command.rb → commands/log_settings_command.rb} +0 -0
  108. data/lib/morpheus/cli/{login.rb → commands/login.rb} +0 -5
  109. data/lib/morpheus/cli/commands/logout.rb +63 -0
  110. data/lib/morpheus/cli/{logs_command.rb → commands/logs_command.rb} +0 -3
  111. data/lib/morpheus/cli/commands/{standard/man_command.rb → man_command.rb} +0 -2
  112. data/lib/morpheus/cli/{monitoring_alerts_command.rb → commands/monitoring_alerts_command.rb} +0 -7
  113. data/lib/morpheus/cli/{monitoring_apps_command.rb → commands/monitoring_apps_command.rb} +0 -1
  114. data/lib/morpheus/cli/{monitoring_checks_command.rb → commands/monitoring_checks_command.rb} +0 -1
  115. data/lib/morpheus/cli/{monitoring_contacts_command.rb → commands/monitoring_contacts_command.rb} +0 -7
  116. data/lib/morpheus/cli/{monitoring_groups_command.rb → commands/monitoring_groups_command.rb} +0 -1
  117. data/lib/morpheus/cli/{monitoring_incidents_command.rb → commands/monitoring_incidents_command.rb} +0 -1
  118. data/lib/morpheus/cli/commands/network_dhcp_relays_command.rb +416 -0
  119. data/lib/morpheus/cli/commands/network_dhcp_servers_command.rb +407 -0
  120. data/lib/morpheus/cli/{network_domains_command.rb → commands/network_domains_command.rb} +0 -4
  121. data/lib/morpheus/cli/commands/network_edge_clusters_command.rb +329 -0
  122. data/lib/morpheus/cli/commands/network_firewalls_command.rb +823 -0
  123. data/lib/morpheus/cli/{network_groups_command.rb → commands/network_groups_command.rb} +0 -4
  124. data/lib/morpheus/cli/{network_pool_servers_command.rb → commands/network_pool_servers_command.rb} +0 -4
  125. data/lib/morpheus/cli/{network_pools_command.rb → commands/network_pools_command.rb} +0 -4
  126. data/lib/morpheus/cli/{network_proxies_command.rb → commands/network_proxies_command.rb} +0 -4
  127. data/lib/morpheus/cli/{network_routers_command.rb → commands/network_routers_command.rb} +387 -57
  128. data/lib/morpheus/cli/{network_services_command.rb → commands/network_services_command.rb} +0 -4
  129. data/lib/morpheus/cli/commands/network_static_routes_command.rb +446 -0
  130. data/lib/morpheus/cli/commands/network_transport_zones_command.rb +452 -0
  131. data/lib/morpheus/cli/{networks_command.rb → commands/networks_command.rb} +20 -20
  132. data/lib/morpheus/cli/commands/open_command.rb +30 -0
  133. data/lib/morpheus/cli/commands/options.rb +98 -0
  134. data/lib/morpheus/cli/{packages_command.rb → commands/packages_command.rb} +0 -2
  135. data/lib/morpheus/cli/{ping.rb → commands/ping.rb} +0 -7
  136. data/lib/morpheus/cli/{policies_command.rb → commands/policies_command.rb} +1 -8
  137. data/lib/morpheus/cli/{power_schedules_command.rb → commands/power_schedules_command.rb} +0 -0
  138. data/lib/morpheus/cli/{preseed_scripts_command.rb → commands/preseed_scripts_command.rb} +0 -3
  139. data/lib/morpheus/cli/{price_sets_command.rb → commands/price_sets_command.rb} +0 -0
  140. data/lib/morpheus/cli/{prices_command.rb → commands/prices_command.rb} +7 -7
  141. data/lib/morpheus/cli/{processes_command.rb → commands/processes_command.rb} +0 -1
  142. data/lib/morpheus/cli/{projects_command.rb → commands/projects_command.rb} +0 -0
  143. data/lib/morpheus/cli/{provisioning_licenses_command.rb → commands/provisioning_licenses_command.rb} +0 -0
  144. data/lib/morpheus/cli/{provisioning_settings_command.rb → commands/provisioning_settings_command.rb} +0 -0
  145. data/lib/morpheus/cli/{recent_activity_command.rb → commands/recent_activity_command.rb} +0 -0
  146. data/lib/morpheus/cli/{remote.rb → commands/remote.rb} +5 -9
  147. data/lib/morpheus/cli/{reports_command.rb → commands/reports_command.rb} +0 -2
  148. data/lib/morpheus/cli/commands/{standard/rm_command.rb → rm_command.rb} +0 -0
  149. data/lib/morpheus/cli/{roles.rb → commands/roles.rb} +245 -40
  150. data/lib/morpheus/cli/{search_command.rb → commands/search_command.rb} +0 -0
  151. data/lib/morpheus/cli/{security_group_rules.rb → commands/security_group_rules.rb} +0 -5
  152. data/lib/morpheus/cli/{security_groups.rb → commands/security_groups.rb} +0 -6
  153. data/lib/morpheus/cli/{service_catalog_command.rb → commands/service_catalog_command.rb} +0 -0
  154. data/lib/morpheus/cli/{service_plans_command.rb → commands/service_plans_command.rb} +0 -0
  155. data/lib/morpheus/cli/commands/{standard/set_prompt_command.rb → set_prompt_command.rb} +0 -3
  156. data/lib/morpheus/cli/{setup.rb → commands/setup.rb} +0 -0
  157. data/lib/morpheus/cli/{shell.rb → commands/shell.rb} +4 -105
  158. data/lib/morpheus/cli/commands/{standard/sleep_command.rb → sleep_command.rb} +0 -2
  159. data/lib/morpheus/cli/commands/{standard/source_command.rb → source_command.rb} +0 -2
  160. data/lib/morpheus/cli/commands/{standard/ssl_verification_command.rb → ssl_verification_command.rb} +0 -3
  161. data/lib/morpheus/cli/{storage_providers_command.rb → commands/storage_providers_command.rb} +0 -4
  162. data/lib/morpheus/cli/commands/storage_server_types.rb +50 -0
  163. data/lib/morpheus/cli/commands/storage_servers.rb +122 -0
  164. data/lib/morpheus/cli/commands/storage_volume_types.rb +50 -0
  165. data/lib/morpheus/cli/commands/storage_volumes.rb +103 -0
  166. data/lib/morpheus/cli/{subnets_command.rb → commands/subnets_command.rb} +7 -6
  167. data/lib/morpheus/cli/{tasks.rb → commands/tasks.rb} +25 -6
  168. data/lib/morpheus/cli/commands/{standard/tee_command.rb → tee_command.rb} +0 -0
  169. data/lib/morpheus/cli/{tenants_command.rb → commands/tenants_command.rb} +1 -8
  170. data/lib/morpheus/cli/commands/{standard/update_command.rb → update_command.rb} +0 -1
  171. data/lib/morpheus/cli/{usage_command.rb → commands/usage_command.rb} +0 -0
  172. data/lib/morpheus/cli/{user_groups_command.rb → commands/user_groups_command.rb} +1 -2
  173. data/lib/morpheus/cli/{user_settings_command.rb → commands/user_settings_command.rb} +2 -1
  174. data/lib/morpheus/cli/{user_sources_command.rb → commands/user_sources_command.rb} +1 -2
  175. data/lib/morpheus/cli/{users.rb → commands/users.rb} +28 -35
  176. data/lib/morpheus/cli/{vdi_allocations_command.rb → commands/vdi_allocations_command.rb} +0 -0
  177. data/lib/morpheus/cli/{vdi_apps_command.rb → commands/vdi_apps_command.rb} +0 -0
  178. data/lib/morpheus/cli/{vdi_command.rb → commands/vdi_command.rb} +0 -0
  179. data/lib/morpheus/cli/{vdi_gateways_command.rb → commands/vdi_gateways_command.rb} +0 -0
  180. data/lib/morpheus/cli/{vdi_pools_command.rb → commands/vdi_pools_command.rb} +0 -0
  181. data/lib/morpheus/cli/commands/{standard/version_command.rb → version_command.rb} +0 -0
  182. data/lib/morpheus/cli/commands/view.rb +102 -0
  183. data/lib/morpheus/cli/{virtual_images.rb → commands/virtual_images.rb} +2 -4
  184. data/lib/morpheus/cli/{whitelabel_settings_command.rb → commands/whitelabel_settings_command.rb} +0 -1
  185. data/lib/morpheus/cli/{whoami.rb → commands/whoami.rb} +0 -4
  186. data/lib/morpheus/cli/{wiki_command.rb → commands/wiki_command.rb} +0 -5
  187. data/lib/morpheus/cli/{workflows.rb → commands/workflows.rb} +0 -3
  188. data/lib/morpheus/cli/mixins/accounts_helper.rb +5 -5
  189. data/lib/morpheus/cli/mixins/load_balancers_helper.rb +26 -6
  190. data/lib/morpheus/cli/mixins/logs_helper.rb +1 -1
  191. data/lib/morpheus/cli/mixins/print_helper.rb +51 -18
  192. data/lib/morpheus/cli/mixins/processes_helper.rb +1 -2
  193. data/lib/morpheus/cli/mixins/provisioning_helper.rb +48 -17
  194. data/lib/morpheus/cli/mixins/rest_command.rb +270 -94
  195. data/lib/morpheus/cli/mixins/secondary_rest_command.rb +667 -0
  196. data/lib/morpheus/cli/mixins/storage_servers_helper.rb +156 -0
  197. data/lib/morpheus/cli/mixins/storage_volumes_helper.rb +119 -0
  198. data/lib/morpheus/cli/option_types.rb +149 -40
  199. data/lib/morpheus/cli/version.rb +1 -1
  200. data/lib/morpheus/cli.rb +6 -128
  201. data/lib/morpheus/ext/string.rb +29 -6
  202. data/lib/morpheus/routes.rb +238 -0
  203. data/lib/morpheus/terminal.rb +5 -6
  204. data/lib/morpheus/util.rb +6 -1
  205. metadata +176 -141
  206. data/lib/morpheus/cli/change_password_command.rb +0 -147
  207. data/lib/morpheus/cli/library.rb +0 -1
  208. data/lib/morpheus/cli/load_balancers.rb +0 -245
  209. data/lib/morpheus/cli/logout.rb +0 -81
@@ -0,0 +1,667 @@
1
+ # SecondaryRestCommand is a mixin for Morpheus::Cli command classes.
2
+ # for resources that are secondary to some parent resource.
3
+ # Provides basic CRUD commands: list, get, add, update, remove
4
+ # The parent resource is specified as the first argument for all the comments.
5
+ #
6
+ # Example of a SecondaryRestCommand for `morpheus load-balancer-virtual-servers`.
7
+ #
8
+ # class Morpheus::Cli::LoadBalancerVirtualServers
9
+ #
10
+ # include Morpheus::Cli::CliCommand
11
+ # include Morpheus::Cli::RestCommand
12
+ # include Morpheus::Cli::SecondaryRestCommand
13
+ # include Morpheus::Cli::LoadBalancersHelper
14
+ #
15
+ # set_command_name :'load-balancer-virtual-servers'
16
+ # register_subcommands :list, :get, :add, :update, :remove
17
+ #
18
+ # register_interfaces :load_balancer_virtual_servers,
19
+ # :load_balancers, :load_balancer_types
20
+ #
21
+ # set_rest_parent_name :load_balancers
22
+ #
23
+ # end
24
+ #
25
+ module Morpheus::Cli::SecondaryRestCommand
26
+ def self.included(base)
27
+ base.extend ClassMethods
28
+ end
29
+
30
+ module ClassMethods
31
+
32
+ ## duplicated the rest_* settings with rest_parent_*, for defining the parent resource
33
+
34
+ # rest_parent_name is the rest_name for the parent
35
+ def rest_parent_name
36
+ @rest_parent_name || default_rest_parent_name
37
+ end
38
+
39
+ def default_rest_parent_name
40
+ words = rest_name.split("_")
41
+ if words.size > 1
42
+ words.pop
43
+ return words.join("_") + "s"
44
+ else
45
+ # this wont happen, default wont make sense in this scenario
46
+ # "parent_" + rest_name
47
+ raise "Unable to determine default_rest_parent_name for rest_name: #{rest_name}, class: #{self}"
48
+ end
49
+ end
50
+
51
+ def rest_parent_name=(v)
52
+ @rest_parent_name = v.to_s
53
+ end
54
+
55
+ alias :set_rest_parent_name :rest_parent_name=
56
+ alias :set_rest_parent :rest_parent_name=
57
+ #alias :rest_parent= :rest_parent_name=
58
+
59
+ # rest_parent_key is the singular name of the resource eg. "neat_thing"
60
+ def rest_parent_key
61
+ @rest_parent_key || default_rest_parent_key
62
+ end
63
+
64
+ def default_rest_parent_key
65
+ rest_parent_name.chomp("s")
66
+ end
67
+
68
+ def rest_parent_key=(v)
69
+ @rest_parent_key = v.to_s
70
+ end
71
+
72
+ alias :set_rest_parent_key :rest_parent_key=
73
+
74
+ def rest_parent_arg
75
+ @rest_parent_arg || default_rest_parent_arg
76
+ end
77
+
78
+ def default_rest_parent_arg
79
+ rest_parent_key.to_s.gsub("_", " ")
80
+ end
81
+
82
+ def rest_parent_arg=(v)
83
+ @rest_parent_arg = v.to_s
84
+ end
85
+
86
+ alias :set_rest_parent_arg :rest_parent_arg=
87
+
88
+ def rest_parent_param
89
+ @rest_parent_param || default_rest_parent_param
90
+ end
91
+
92
+ def default_rest_parent_param
93
+ param = rest_parent_key.to_s.split('_').collect(&:capitalize).join
94
+ "#{param[0].downcase}#{param[1..-1]}Id"
95
+ end
96
+
97
+ def rest_parent_param=(v)
98
+ @rest_parent_param = v.to_s
99
+ end
100
+
101
+ alias :set_rest_parent_param :rest_parent_param=
102
+
103
+ # rest_parent_has_name indicates a resource has a name and can be retrieved by name or id
104
+ # true by default, set to false for lookups by only id
105
+ def rest_parent_has_name
106
+ @rest_parent_has_name != nil ? @rest_parent_has_name : default_rest_parent_has_name
107
+ end
108
+
109
+ def default_rest_parent_has_name
110
+ true
111
+ end
112
+
113
+ def rest_parent_has_name=(v)
114
+ @rest_parent_has_name = !!v
115
+ end
116
+
117
+ alias :set_rest_parent_has_name :rest_parent_has_name=
118
+
119
+ # rest_parent_label is the capitalized resource label eg. "Neat Thing"
120
+ def rest_parent_label
121
+ @rest_parent_label || default_rest_parent_label
122
+ end
123
+
124
+ def default_rest_parent_label
125
+ rest_parent_key.to_s.split("_").collect {|it| it.to_s.capitalize }.join(" ")
126
+ end
127
+
128
+ def rest_parent_label=(v)
129
+ @rest_parent_label = v.to_s
130
+ end
131
+
132
+ alias :set_rest_parent_label :rest_parent_label=
133
+
134
+ # the plural version of the label eg. "Neat Things"
135
+ def rest_parent_label_plural
136
+ @rest_parent_label_plural || default_rest_parent_label_plural
137
+ end
138
+
139
+ def default_rest_parent_label_plural
140
+ #rest_parent_name.to_s.split("_").collect {|it| it.to_s.capitalize }.join(" ")
141
+ rest_parent_label.to_s.pluralize
142
+ end
143
+
144
+ def rest_parent_label_plural=(v)
145
+ @rest_parent_label_plural = v.to_s
146
+ end
147
+
148
+ alias :set_rest_parent_label_plural :rest_parent_label_plural=
149
+
150
+ # the name of the default interface, matches the rest name eg. "neat_things"
151
+ def rest_parent_interface_name
152
+ @rest_parent_interface_name || default_rest_parent_interface_name
153
+ end
154
+
155
+ def default_rest_parent_interface_name
156
+ rest_parent_name
157
+ end
158
+
159
+ def rest_parent_interface_name=(v)
160
+ @rest_parent_interface_name = v.to_s
161
+ end
162
+
163
+ alias :set_rest_parent_interface_name :rest_parent_interface_name=
164
+
165
+ end
166
+
167
+ ## duplicated the rest_* settings with rest_parent, for the parents resource
168
+
169
+ def rest_parent_name
170
+ self.class.rest_parent_name
171
+ end
172
+
173
+ def rest_parent_key
174
+ self.class.rest_parent_key
175
+ end
176
+
177
+ def rest_parent_arg
178
+ self.class.rest_parent_arg
179
+ end
180
+
181
+ def rest_parent_param
182
+ self.class.rest_parent_param
183
+ end
184
+
185
+ def rest_parent_has_name
186
+ self.class.rest_parent_has_name
187
+ end
188
+
189
+ def rest_parent_label
190
+ self.class.rest_parent_label
191
+ end
192
+
193
+ def rest_parent_label_plural
194
+ self.class.rest_parent_label_plural
195
+ end
196
+
197
+ def rest_parent_interface_name
198
+ self.class.rest_parent_interface_name # || "@#{rest_parent_name}_interface"
199
+ end
200
+
201
+ def rest_parent_interface
202
+ instance_variable_get("@#{rest_parent_interface_name}_interface")
203
+ end
204
+
205
+ def rest_parent_object_key
206
+ send("#{rest_parent_key}_object_key")
207
+ end
208
+
209
+ def rest_parent_list_key
210
+ send("#{rest_parent_key}_list_key")
211
+ end
212
+
213
+ def rest_parent_column_definitions(options)
214
+ send("#{rest_parent_key}_column_definitions", options)
215
+ end
216
+
217
+ def rest_parent_list_column_definitions(options)
218
+ send("#{rest_parent_key}_list_column_definitions", options)
219
+ end
220
+
221
+ def rest_parent_find_by_name_or_id(val)
222
+ # use explicitly defined finders
223
+ # else default to new generic CliCommand find_by methods
224
+ if rest_parent_has_name
225
+ if respond_to?("find_#{rest_parent_key}_by_name_or_id", true)
226
+ send("find_#{rest_parent_key}_by_name_or_id", val)
227
+ else
228
+ find_by_name_or_id(rest_parent_key, val)
229
+ end
230
+ else
231
+ if respond_to?("find_#{rest_parent_key}_by_id", true)
232
+ send("find_#{rest_parent_key}_by_id", val)
233
+ else
234
+ find_by_id(rest_parent_key, val)
235
+ end
236
+ end
237
+ end
238
+
239
+ # override RestCommand method to include parent_id parameter
240
+ def rest_find_by_name_or_id(parent_id, val)
241
+ # use explicitly defined finders
242
+ # else default to new generic CliCommand find_by methods
243
+ if rest_has_name
244
+ if respond_to?("find_#{rest_key}_by_name_or_id", true)
245
+ send("find_#{rest_key}_by_name_or_id", parent_id, val)
246
+ else
247
+ find_by_name_or_id(rest_key, parent_id, val)
248
+ end
249
+ else
250
+ if respond_to?("find_#{rest_key}_by_id", true)
251
+ send("find_#{rest_key}_by_id", parent_id, val)
252
+ else
253
+ find_by_id(rest_key, parent_id, val)
254
+ end
255
+ end
256
+ end
257
+
258
+ def registered_interfaces
259
+ self.class.registered_interfaces
260
+ end
261
+
262
+ def list(args)
263
+ parent_id, parent_record = nil, nil
264
+ params = {}
265
+ options = {}
266
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
267
+ opts.banner = subcommand_usage("[#{rest_parent_arg}] [search]")
268
+ build_list_options(opts, options, params)
269
+ opts.footer = <<-EOT
270
+ List #{rest_label_plural.downcase}.
271
+ [#{rest_parent_arg}] is required. This is the #{rest_parent_has_name ? 'name or id' : 'id'} of #{a_or_an(rest_parent_label)} #{rest_parent_label.downcase}.
272
+ [search] is optional. This is a search phrase to filter the results.
273
+ EOT
274
+ end
275
+ optparse.parse!(args)
276
+ verify_args!(args:args, optparse:optparse, min:1)
277
+ connect(options)
278
+ parent_id = args[0]
279
+ parent_record = rest_parent_find_by_name_or_id(parent_id)
280
+ if parent_record.nil?
281
+ return 1, "#{rest_parent_label} not found for '#{parent_id}"
282
+ end
283
+ parent_id = parent_record['id']
284
+ parse_list_options!(args.count > 1 ? args[1..-1] : [], options, params)
285
+ rest_interface.setopts(options)
286
+ if options[:dry_run]
287
+ print_dry_run rest_interface.dry.list(parent_id, params)
288
+ return
289
+ end
290
+ json_response = rest_interface.list(parent_id, params)
291
+ render_response(json_response, options, rest_list_key) do
292
+ records = json_response[rest_list_key]
293
+ print_h1 "Morpheus #{rest_label_plural}"
294
+ if records.nil? || records.empty?
295
+ print cyan,"No #{rest_label_plural.downcase} found.",reset,"\n"
296
+ else
297
+ print as_pretty_table(records, rest_list_column_definitions(options).upcase_keys!, options)
298
+ print_results_pagination(json_response) if json_response['meta']
299
+ end
300
+ print reset,"\n"
301
+ end
302
+ return 0, nil
303
+ end
304
+
305
+ def get(args)
306
+ params = {}
307
+ options = {}
308
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
309
+ opts.banner = subcommand_usage("[#{rest_parent_arg}] [#{rest_arg}]")
310
+ build_get_options(opts, options, params)
311
+ opts.footer = <<-EOT
312
+ Get details about #{a_or_an(rest_label)} #{rest_label.downcase}.
313
+ [#{rest_parent_arg}] is required. This is the #{rest_parent_has_name ? 'name or id' : 'id'} of #{a_or_an(rest_parent_label)} #{rest_parent_label.downcase}.
314
+ [#{rest_arg}] is required. This is the #{rest_has_name ? 'name or id' : 'id'} of #{a_or_an(rest_label)} #{rest_label.downcase}.
315
+ EOT
316
+ end
317
+ optparse.parse!(args)
318
+ verify_args!(args:args, optparse:optparse, min:2)
319
+ connect(options)
320
+ parse_get_options!(args.count > 1 ? args[1..-1] : [], options, params)
321
+ parent_id = args[0]
322
+ parent_record = rest_parent_find_by_name_or_id(parent_id)
323
+ if parent_record.nil?
324
+ return 1, "#{rest_parent_label} not found for '#{parent_id}"
325
+ end
326
+ parent_id = parent_record['id']
327
+ id = args[1..-1].join(" ")
328
+ _get(parent_id, id, params, options)
329
+ end
330
+
331
+ def _get(parent_id, id, params, options)
332
+ if id !~ /\A\d{1,}\Z/
333
+ record = rest_find_by_name_or_id(parent_id, id)
334
+ if record.nil?
335
+ return 1, "#{rest_label} not found for '#{id}"
336
+ end
337
+ id = record['id']
338
+ end
339
+ rest_interface.setopts(options)
340
+ if options[:dry_run]
341
+ print_dry_run rest_interface.dry.get(parent_id, id, params)
342
+ return
343
+ end
344
+ json_response = rest_interface.get(parent_id, id, params)
345
+ render_response_for_get(json_response, options)
346
+ return 0, nil
347
+ end
348
+
349
+ def render_response_for_get(json_response, options)
350
+ render_response(json_response, options, rest_object_key) do
351
+ record = json_response[rest_object_key]
352
+ print_h1 rest_label, [], options
353
+ print cyan
354
+ print_description_list(rest_column_definitions(options), record, options)
355
+ # show config settings...
356
+ if record['optionTypes'] && record['optionTypes'].size > 0
357
+ print_h2 "Option Types", options
358
+ print format_option_types_table(record['optionTypes'], options, rest_object_key)
359
+ end
360
+ print reset,"\n"
361
+ end
362
+ end
363
+
364
+ def add(args)
365
+ parent_id, parent_record = nil, nil
366
+ record_type_id = nil
367
+ options = {}
368
+ option_types = respond_to?("add_#{rest_key}_option_types", true) ? send("add_#{rest_key}_option_types") : []
369
+ advanced_option_types = respond_to?("add_#{rest_key}_advanced_option_types", true) ? send("add_#{rest_key}_advanced_option_types") : []
370
+ type_option_type = option_types.find {|it| it['fieldName'] == 'type'}
371
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
372
+ opts.banner = subcommand_usage("[#{rest_parent_arg}] [#{rest_arg}]")
373
+ if rest_has_type && type_option_type.nil?
374
+ opts.on( '-t', "--#{rest_type_arg} TYPE", "#{rest_type_label}" ) do |val|
375
+ record_type_id = val
376
+ end
377
+ end
378
+ build_option_type_options(opts, options, option_types)
379
+ build_option_type_options(opts, options, advanced_option_types)
380
+ build_standard_add_options(opts, options)
381
+ opts.footer = <<-EOT
382
+ Create a new #{rest_label.downcase}.
383
+ [#{rest_parent_arg}] is required. This is the #{rest_parent_has_name ? 'name or id' : 'id'} of #{a_or_an(rest_parent_label)} #{rest_parent_label.downcase}.
384
+ [#{rest_arg}] is required. This is the name of the new #{rest_label.downcase}.
385
+ EOT
386
+ end
387
+ optparse.parse!(args)
388
+ verify_args!(args:args, optparse:optparse, min:1, max: 2)
389
+ # todo: make supporting args[0] optional and more flexible
390
+ # for now args[0] is assumed to be the 'name'
391
+ record_name = nil
392
+ parent_id = args[0]
393
+ if rest_has_name
394
+ if args[1]
395
+ record_name = args[1]
396
+ end
397
+ verify_args!(args:args, optparse:optparse, min:1, max: 2)
398
+ else
399
+ verify_args!(args:args, optparse:optparse, count: 1)
400
+ end
401
+ connect(options)
402
+ # load parent record
403
+ # todo: prompt instead of error
404
+ parent_record = rest_parent_find_by_name_or_id(parent_id)
405
+ if parent_record.nil?
406
+ return 1, "#{rest_parent_label} not found for '#{parent_id}"
407
+ end
408
+ parent_id = parent_record['id']
409
+ # load or prompt for type
410
+ if rest_has_type && type_option_type.nil?
411
+ if record_type_id.nil?
412
+ #raise_command_error "#{rest_type_label} is required.\n#{optparse}"
413
+ type_list = rest_type_interface.list({max:10000, creatable: true})[rest_type_list_key]
414
+ type_dropdown_options = type_list.collect {|it| {'name' => it['name'], 'value' => it['code']} }
415
+ record_type_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'type', 'fieldLabel' => rest_type_label, 'type' => 'select', 'selectOptions' => type_dropdown_options, 'required' => true}], options[:options], @api_client)['type']
416
+ end
417
+ record_type = rest_type_find_by_name_or_id(record_type_id)
418
+ if record_type.nil?
419
+ return 1, "#{rest_type_label} not found for '#{record_type_id}"
420
+ end
421
+ end
422
+ passed_options = parse_passed_options(options)
423
+ options[:params] ||= {}
424
+ options[:params][rest_parent_param] = parent_id
425
+ options[:options]['_object_key'] = rest_object_key
426
+ payload = {}
427
+ if options[:payload]
428
+ payload = options[:payload]
429
+ payload.deep_merge!({rest_object_key => passed_options})
430
+ else
431
+ record_payload = {}
432
+ if record_name
433
+ record_payload['name'] = record_name
434
+ options[:options]['name'] = record_name # injected for prompt
435
+ options[:options][rest_arg] = record_name
436
+ end
437
+ if rest_has_type && record_type
438
+ # record_payload['type'] = {'code' => record_type['code']}
439
+ record_payload['type'] = record_type['code']
440
+ options[:options]['type'] = record_type['code'] # injected for prompt
441
+ # initialize params for loading optionSource data
442
+ options[:params]['type'] = record_type['code']
443
+ end
444
+ record_payload.deep_merge!(passed_options)
445
+ if option_types && !option_types.empty?
446
+ v_prompt = Morpheus::Cli::OptionTypes.prompt(option_types, options[:options], @api_client, options[:params])
447
+ v_prompt.deep_compact!
448
+ v_prompt.booleanize! # 'on' => true
449
+ record_payload.deep_merge!(v_prompt)
450
+ end
451
+ # options by type
452
+ if rest_has_type && record_type.nil?
453
+ type_value = record_payload['type'].is_a?(Hash) ? record_payload['type']['id'] : record_payload['type']
454
+ if type_value
455
+ record_type = rest_type_find_by_name_or_id(type_value)
456
+ if record_type.nil?
457
+ return 1, "#{rest_type_label} not found for '#{type_value}"
458
+ end
459
+ end
460
+ # reload the type by id to get all the details ie. optionTypes
461
+ if record_type && record_type['optionTypes'].nil?
462
+ record_type = rest_type_find_by_name_or_id(record_type['id'])
463
+ end
464
+ end
465
+ if respond_to?("load_option_types_for_#{rest_key}", true)
466
+ my_option_types = send("load_option_types_for_#{rest_key}", record_type, parent_record)
467
+ else
468
+ my_option_types = record_type ? record_type['optionTypes'] : nil
469
+ end
470
+ if my_option_types && !my_option_types.empty?
471
+ # remove redundant fieldContext
472
+ my_option_types.each do |option_type|
473
+ if option_type['fieldContext'] == rest_object_key
474
+ option_type['fieldContext'] = nil
475
+ end
476
+ end
477
+ api_params = (options[:params] || {}).merge(record_payload)
478
+ v_prompt = Morpheus::Cli::OptionTypes.prompt(my_option_types, options[:options], @api_client, api_params)
479
+ v_prompt.deep_compact!
480
+ v_prompt.booleanize! # 'on' => true
481
+ record_payload.deep_merge!(v_prompt)
482
+ end
483
+ # advanced options (uses no_prompt)
484
+ if advanced_option_types && !advanced_option_types.empty?
485
+ v_prompt = Morpheus::Cli::OptionTypes.no_prompt(advanced_option_types, options[:options], @api_client, options[:params])
486
+ v_prompt.deep_compact!
487
+ v_prompt.booleanize! # 'on' => true
488
+ record_payload.deep_merge!(v_prompt)
489
+ end
490
+ payload[rest_object_key] = record_payload
491
+ end
492
+ rest_interface.setopts(options)
493
+ if options[:dry_run]
494
+ print_dry_run rest_interface.dry.create(parent_id, payload)
495
+ return
496
+ end
497
+ json_response = rest_interface.create(parent_id, payload)
498
+ render_response(json_response, options, rest_object_key) do
499
+ record = json_response[rest_object_key]
500
+ print_green_success "Added #{rest_label.downcase} #{record['name'] || record['id']}"
501
+ return _get(parent_id, record["id"], {}, options)
502
+ end
503
+ return 0, nil
504
+ end
505
+
506
+ def update(args)
507
+ parent_id = args[0]
508
+ id = args[1]
509
+ record_type = nil
510
+ record_type_id = nil
511
+ options = {}
512
+ option_types = respond_to?("update_#{rest_key}_option_types", true) ? send("update_#{rest_key}_option_types") : []
513
+ advanced_option_types = respond_to?("update_#{rest_key}_advanced_option_types", true) ? send("update_#{rest_key}_advanced_option_types") : []
514
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
515
+ opts.banner = subcommand_usage("[#{rest_parent_arg}] [#{rest_arg}] [options]")
516
+ build_standard_update_options(opts, options)
517
+ opts.footer = <<-EOT
518
+ Update an existing #{rest_label.downcase}.
519
+ [#{rest_parent_arg}] is required. This is the #{rest_parent_has_name ? 'name or id' : 'id'} of #{a_or_an(rest_parent_label)} #{rest_parent_label.downcase}.
520
+ [#{rest_arg}] is required. This is the #{rest_has_name ? 'name or id' : 'id'} of #{a_or_an(rest_label)} #{rest_label.downcase}.
521
+ EOT
522
+ end
523
+ optparse.parse!(args)
524
+ verify_args!(args:args, optparse:optparse, count:2)
525
+ connect(options)
526
+ parent_record = rest_parent_find_by_name_or_id(parent_id)
527
+ if parent_record.nil?
528
+ return 1, "#{rest_parent_label} not found for '#{parent_id}"
529
+ end
530
+ parent_id = parent_record['id']
531
+ connect(options)
532
+ record = rest_find_by_name_or_id(parent_id, id)
533
+ if record.nil?
534
+ return 1, "#{rest_name} not found for '#{id}'"
535
+ end
536
+ # load type so we can prompt for those option types
537
+ if rest_has_type
538
+ record_type_id = record['type']['id']
539
+ record_type = rest_type_find_by_name_or_id(record_type_id)
540
+ if record_type.nil?
541
+ return 1, "#{rest_type_label} not found for '#{record_type_id}"
542
+ end
543
+ # reload the type by id to get all the details ie. optionTypes
544
+ if record_type['optionTypes'].nil?
545
+ record_type = rest_type_find_by_name_or_id(record_type['id'])
546
+ end
547
+ end
548
+ passed_options = parse_passed_options(options)
549
+ payload = {}
550
+ if options[:payload]
551
+ payload = options[:payload]
552
+ payload.deep_merge!({rest_object_key => passed_options}) unless passed_options.empty?
553
+ else
554
+ record_payload = passed_options
555
+ if rest_has_type && record_type
556
+ # inject type to options for prompting
557
+ # record_payload['type'] = record_type['code']
558
+ # options[:options]['type'] = record_type['code']
559
+ # initialize params for loading optionSource data
560
+ options[:params] ||= {}
561
+ options[:params]['type'] = record_type['code']
562
+ end
563
+ # update options without prompting by default
564
+ if false && option_types && !option_types.empty?
565
+ api_params = (options[:params] || {}).merge(record_payload) # need to merge in values from record too, ughhh
566
+ v_prompt = Morpheus::Cli::OptionTypes.no_prompt(option_types, options[:options], @api_client, api_params)
567
+ v_prompt.deep_compact!
568
+ v_prompt.booleanize! # 'on' => true
569
+ record_payload.deep_merge!(v_prompt)
570
+ end
571
+ # options by type
572
+ my_option_types = nil
573
+ if respond_to?("load_option_types_for_#{rest_key}", true)
574
+ my_option_types = send("load_option_types_for_#{rest_key}", record_type, parent_record)
575
+ else
576
+ my_option_types = record_type ? record_type['optionTypes'] : nil
577
+ end
578
+ if false && my_option_types && !my_option_types.empty?
579
+ # remove redundant fieldContext
580
+ # make them optional for updates
581
+ # todo: use current value as default instead of just making things optioanl
582
+ # maybe new prompt() options like {:mode => :edit, :object => storage_server} or something
583
+ my_option_types.each do |option_type|
584
+ if option_type['fieldContext'] == rest_object_key
585
+ option_type['fieldContext'] = nil
586
+ end
587
+ option_type.delete('required')
588
+ option_type.delete('defaultValue')
589
+ end
590
+ v_prompt = Morpheus::Cli::OptionTypes.no_prompt(my_option_types, options[:options], @api_client, options[:params])
591
+ v_prompt.deep_compact!
592
+ v_prompt.booleanize! # 'on' => true
593
+ record_payload.deep_merge!(v_prompt)
594
+ end
595
+ # advanced options
596
+ if false && advanced_option_types && !advanced_option_types.empty?
597
+ v_prompt = Morpheus::Cli::OptionTypes.no_prompt(advanced_option_types, options[:options], @api_client, options[:params])
598
+ v_prompt.deep_compact!
599
+ v_prompt.booleanize! # 'on' => true
600
+ record_payload.deep_merge!(v_prompt)
601
+ end
602
+ # remove empty config, compact could hanlde this
603
+ if record_payload['config'] && record_payload['config'].empty?
604
+ record_payload.delete('config')
605
+ end
606
+ # prevent updating with empty payload
607
+ if record_payload.empty?
608
+ raise_command_error "Specify at least one option to update.\n#{optparse}"
609
+ end
610
+ payload[rest_object_key] = record_payload
611
+ end
612
+ rest_interface.setopts(options)
613
+ if options[:dry_run]
614
+ print_dry_run rest_interface.dry.update(parent_id, record['id'], payload)
615
+ return
616
+ end
617
+ json_response = rest_interface.update(parent_id, record['id'], payload)
618
+ render_response(json_response, options, rest_object_key) do
619
+ print_green_success "Updated #{rest_label.downcase} #{record['name'] || record['id']}"
620
+ _get(parent_id, record["id"], {}, options)
621
+ end
622
+ return 0, nil
623
+ end
624
+
625
+ def remove(args)
626
+ parent_id = args[0]
627
+ id = args[1]
628
+ params = {}
629
+ options = {}
630
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
631
+ opts.banner = subcommand_usage("[#{rest_parent_arg}] [#{rest_arg}]")
632
+ build_standard_remove_options(opts, options)
633
+ opts.footer = <<-EOT
634
+ Delete an existing #{rest_label.downcase}.
635
+ [#{rest_parent_arg}] is required. This is the #{rest_parent_has_name ? 'name or id' : 'id'} of #{a_or_an(rest_parent_label)} #{rest_parent_label.downcase}.
636
+ [#{rest_arg}] is required. This is the #{rest_has_name ? 'name or id' : 'id'} of #{a_or_an(rest_label)} #{rest_label.downcase}.
637
+ EOT
638
+ end
639
+ optparse.parse!(args)
640
+ verify_args!(args:args, optparse:optparse, count:2)
641
+ connect(options)
642
+ parent_record = rest_parent_find_by_name_or_id(parent_id)
643
+ if parent_record.nil?
644
+ return 1, "#{rest_parent_label} not found for '#{parent_id}"
645
+ end
646
+ record = rest_find_by_name_or_id(parent_record['id'], id)
647
+ if record.nil?
648
+ return 1, "#{rest_name} not found for '#{id}'"
649
+ end
650
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the #{rest_label.downcase} #{record['name'] || record['id']}?")
651
+ return 9, "aborted"
652
+ end
653
+ params.merge!(parse_query_options(options))
654
+ rest_interface.setopts(options)
655
+ if options[:dry_run]
656
+ print_dry_run rest_interface.dry.destroy(parent_id, record['id'], params)
657
+ return 0, nil
658
+ end
659
+ json_response = rest_interface.destroy(parent_id, record['id'], params)
660
+ render_response(json_response, options) do
661
+ print_green_success "Removed #{rest_label.downcase} #{record['name'] || record['id']}"
662
+ end
663
+ return 0, nil
664
+ end
665
+
666
+ end
667
+