morpheus-cli 4.0.0.1 → 4.1.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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/lib/morpheus/api.rb +10 -0
  3. data/lib/morpheus/api/api_client.rb +24 -3
  4. data/lib/morpheus/api/clouds_interface.rb +15 -0
  5. data/lib/morpheus/api/clusters_interface.rb +276 -0
  6. data/lib/morpheus/api/library_compute_type_layouts_interface.rb +26 -0
  7. data/lib/morpheus/api/logs_interface.rb +6 -0
  8. data/lib/morpheus/api/network_subnet_types_interface.rb +26 -0
  9. data/lib/morpheus/api/network_subnets_interface.rb +47 -0
  10. data/lib/morpheus/api/provision_types_interface.rb +6 -0
  11. data/lib/morpheus/api/security_groups_interface.rb +12 -3
  12. data/lib/morpheus/api/servers_interface.rb +15 -0
  13. data/lib/morpheus/api/service_plans_interface.rb +30 -0
  14. data/lib/morpheus/api/subnets_interface.rb +47 -0
  15. data/lib/morpheus/cli.rb +1 -0
  16. data/lib/morpheus/cli/apps.rb +20 -18
  17. data/lib/morpheus/cli/cli_command.rb +5 -1
  18. data/lib/morpheus/cli/clusters.rb +3952 -0
  19. data/lib/morpheus/cli/containers_command.rb +70 -2
  20. data/lib/morpheus/cli/hosts.rb +69 -53
  21. data/lib/morpheus/cli/instances.rb +33 -33
  22. data/lib/morpheus/cli/library_container_types_command.rb +2 -1
  23. data/lib/morpheus/cli/library_option_lists_command.rb +13 -8
  24. data/lib/morpheus/cli/mixins/accounts_helper.rb +43 -0
  25. data/lib/morpheus/cli/mixins/print_helper.rb +10 -2
  26. data/lib/morpheus/cli/mixins/provisioning_helper.rb +53 -3
  27. data/lib/morpheus/cli/networks_command.rb +883 -36
  28. data/lib/morpheus/cli/option_types.rb +37 -14
  29. data/lib/morpheus/cli/roles.rb +78 -77
  30. data/lib/morpheus/cli/user_settings_command.rb +34 -5
  31. data/lib/morpheus/cli/version.rb +1 -1
  32. metadata +10 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c6b60209108abab70f0d88711bf7779b71d96e9b8bafed4fc151c2cca8ab309
4
- data.tar.gz: 0be087086ccd2e2eeb28cec0541a15140b69ce232b830dd3c8c9a14a0a62dd48
3
+ metadata.gz: b813008442e454799fbb3ff189d1974ceff53cdf2462f0ed2f0c44926d7f9a89
4
+ data.tar.gz: 022c6e7a91a66e7167945de986239f6598a38075ed69600ef39bf60407bea330
5
5
  SHA512:
6
- metadata.gz: 3a92a52eb2a891c390b411508bd5dccdbaa4001be1e91578fddb19979fd169e4fbb5a890e21115d459de90fe4c441ecf39d158f244005a80dbd778e458591a9b
7
- data.tar.gz: 85e1ea8863afaa9341f1dc35877398724a6c3a08331bab816d18fab5e95ad943a978e9946ae31911e95e9e805b81a724c39ad04dad02404cb17d861d12adaef5
6
+ metadata.gz: 76b7fab3aab20b0e698185b43e87baf1d1b7fe8ad20cb4400a2674edb46d2f43f667896abd4a090f2176cb6795ab339c4190df51e42694d344a60c821b44b29a
7
+ data.tar.gz: 1b697421bf324bfa38ca63f74abe59c7890545ea38c8a340da7f15e4ceba8fa22d868b403fb4105d5e0629a700047cfc91458a459120dbce9dd650d7ce921950
@@ -0,0 +1,10 @@
1
+ require "morpheus/cli/version"
2
+ require 'morpheus/cli/command_error'
3
+ require "morpheus/rest_client"
4
+ require 'morpheus/formatters'
5
+ #require 'morpheus/logging'
6
+ #require 'term/ansicolor'
7
+
8
+ # load interfaces
9
+ require 'morpheus/api/api_client.rb'
10
+ Dir[File.dirname(__FILE__) + "/api/**/*.rb"].each {|file| require file }
@@ -126,7 +126,7 @@ class Morpheus::APIClient
126
126
  if options[:timeout]
127
127
  opts[:timeout] = options[:timeout].to_f
128
128
  end
129
-
129
+
130
130
  # add extra headers, eg. from --header option
131
131
  # headers should be a Hash and not an Array, dont make me split you here!
132
132
  if options[:headers]
@@ -248,6 +248,10 @@ class Morpheus::APIClient
248
248
  Morpheus::ProvisionTypesInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
249
249
  end
250
250
 
251
+ def service_plans
252
+ Morpheus::ServicePlansInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
253
+ end
254
+
251
255
  def load_balancers
252
256
  Morpheus::LoadBalancersInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
253
257
  end
@@ -288,6 +292,10 @@ class Morpheus::APIClient
288
292
  Morpheus::SecurityGroupRulesInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
289
293
  end
290
294
 
295
+ def clusters
296
+ Morpheus::ClustersInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
297
+ end
298
+
291
299
  def accounts
292
300
  Morpheus::AccountsInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
293
301
  end
@@ -347,7 +355,7 @@ class Morpheus::APIClient
347
355
  def setup
348
356
  Morpheus::SetupInterface.new(@base_url).setopts(@options).setopts(@options)
349
357
  end
350
-
358
+
351
359
  def monitoring
352
360
  Morpheus::MonitoringInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
353
361
  end
@@ -382,6 +390,14 @@ class Morpheus::APIClient
382
390
  Morpheus::NetworkTypesInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
383
391
  end
384
392
 
393
+ def network_subnets
394
+ Morpheus::NetworkSubnetsInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
395
+ end
396
+
397
+ def network_subnet_types
398
+ Morpheus::NetworkSubnetTypesInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
399
+ end
400
+
385
401
  def network_groups
386
402
  Morpheus::NetworkGroupsInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
387
403
  end
@@ -454,6 +470,11 @@ class Morpheus::APIClient
454
470
  Morpheus::LibraryContainerTemplatesInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
455
471
  end
456
472
 
473
+
474
+ def library_compute_type_layouts
475
+ Morpheus::LibraryComputeTypeLayoutsInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
476
+ end
477
+
457
478
  def packages
458
479
  Morpheus::PackagesInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
459
480
  end
@@ -465,7 +486,7 @@ class Morpheus::APIClient
465
486
  def old_cypher
466
487
  Morpheus::OldCypherInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
467
488
  end
468
-
489
+
469
490
  def execution_request
470
491
  Morpheus::ExecutionRequestInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
471
492
  end
@@ -15,6 +15,21 @@ class Morpheus::CloudsInterface < Morpheus::APIClient
15
15
  execute(opts)
16
16
  end
17
17
 
18
+ def cloud_type(params)
19
+ url = "#{@base_url}/api/zone-types"
20
+ headers = { params: {}, authorization: "Bearer #{@access_token}" }
21
+
22
+ if params.is_a?(Hash)
23
+ headers[:params].merge!(params)
24
+ elsif params.is_a?(Numeric)
25
+ url = "#{@base_url}/api/zone-types/#{params}"
26
+ elsif params.is_a?(String)
27
+ headers[:params]['name'] = params
28
+ end
29
+ opts = {method: :get, url: url, headers: headers}
30
+ execute(opts)
31
+ end
32
+
18
33
  def get(params=nil)
19
34
  url = "#{@base_url}/api/zones"
20
35
  headers = { params: {}, authorization: "Bearer #{@access_token}" }
@@ -0,0 +1,276 @@
1
+ require 'morpheus/api/api_client'
2
+
3
+ class Morpheus::ClustersInterface < Morpheus::APIClient
4
+ def initialize(access_token, refresh_token,expires_at = nil, base_url=nil, api='clusters')
5
+ @access_token = access_token
6
+ @refresh_token = refresh_token
7
+ @base_url = base_url
8
+ @api_url = "#{base_url}/api/#{api}"
9
+ @expires_at = expires_at
10
+ end
11
+
12
+ def list(params={})
13
+ url = @api_url
14
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
15
+ execute(method: :get, url: url, headers: headers)
16
+ end
17
+
18
+ def get(params={})
19
+ url = @api_url
20
+ headers = { params: {}, authorization: "Bearer #{@access_token}" }
21
+
22
+ if params.is_a?(Hash)
23
+ headers[:params].merge!(params)
24
+ elsif params.is_a?(Numeric)
25
+ url = "#{@api_url}/#{params}"
26
+ elsif params.is_a?(String)
27
+ headers[:params]['name'] = params
28
+ end
29
+ execute(method: :get, url: url, headers: headers)
30
+ end
31
+
32
+ def create(payload)
33
+ url = @api_url
34
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
35
+ execute(method: :post, url: url, headers: headers, payload: payload.to_json)
36
+ end
37
+
38
+ def update(id, payload)
39
+ url = "#{@api_url}/#{id}"
40
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
41
+ execute(method: :put, url: url, headers: headers, payload: payload.to_json)
42
+ end
43
+
44
+ def destroy(id, params={})
45
+ url = "#{@api_url}/#{id}"
46
+ headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
47
+ execute(method: :delete, url: url, headers: headers)
48
+ end
49
+
50
+ alias :delete :destroy
51
+
52
+ def cluster_types(params={})
53
+ url = "#{@base_url}/api/cluster-types"
54
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
55
+ opts = {method: :get, url: url, headers: headers}
56
+ execute(opts)
57
+ end
58
+
59
+ def update_permissions(id, payload)
60
+ url = "#{@api_url}/#{id}/permissions"
61
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
62
+ execute(method: :put, url: url, headers: headers, payload: payload.to_json)
63
+ end
64
+
65
+ def list_jobs(id, params={})
66
+ url = "#{@api_url}/#{id}/jobs"
67
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
68
+ execute(method: :get, url: url, headers: headers)
69
+ end
70
+
71
+ def destroy_job(id, job_id=nil, params={}, payload={})
72
+ url = nil
73
+ if job_id.is_a?(Array)
74
+ url = "#{@api_url}/#{id}/jobs"
75
+ params['jobId'] = job_id
76
+ elsif job_id.is_a?(Numeric) || job_id.is_a?(String)
77
+ url = "#{@api_url}/#{id}/jobs/#{job_id}"
78
+ else
79
+ raise "passed a bad volume_id: #{job_id || '(none)'}" # lazy
80
+ end
81
+ headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
82
+ execute(method: :delete, url: url, headers: headers, payload: payload.to_json)
83
+ end
84
+
85
+ def list_masters(id, params={})
86
+ url = "#{@api_url}/#{id}/masters"
87
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
88
+ execute(method: :get, url: url, headers: headers)
89
+ end
90
+
91
+ def list_workers(id, params={})
92
+ url = "#{@api_url}/#{id}/workers"
93
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
94
+ execute(method: :get, url: url, headers: headers)
95
+ end
96
+
97
+ def list_services(id, params={})
98
+ url = "#{@api_url}/#{id}/services"
99
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
100
+ execute(method: :get, url: url, headers: headers)
101
+ end
102
+
103
+ # this supports multiple ids
104
+ def destroy_service(id, service_id=nil, params={}, payload={})
105
+ url = nil
106
+ if service_id.is_a?(Array)
107
+ url = "#{@api_url}/#{id}/services"
108
+ params['serviceId'] = service_id
109
+ elsif service_id.is_a?(Numeric) || service_id.is_a?(String)
110
+ url = "#{@api_url}/#{id}/services/#{service_id}"
111
+ else
112
+ raise "passed a bad volume_id: #{service_id || '(none)'}" # lazy
113
+ end
114
+ headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
115
+ execute(method: :delete, url: url, headers: headers, payload: payload.to_json)
116
+ end
117
+
118
+ def add_server(id, payload)
119
+ url = "#{@api_url}/#{id}/servers"
120
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
121
+ execute(method: :post, url: url, headers: headers, payload: payload.to_json)
122
+ end
123
+
124
+ def list_volumes(id, params={})
125
+ url = "#{@api_url}/#{id}/volumes"
126
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
127
+ execute(method: :get, url: url, headers: headers)
128
+ end
129
+
130
+ # this supports multiple ids
131
+ def destroy_volume(id, volume_id=nil, params={}, payload={})
132
+ url = nil
133
+ if volume_id.is_a?(Array)
134
+ url = "#{@api_url}/#{id}/volumes"
135
+ params['volumeId'] = volume_id
136
+ elsif volume_id.is_a?(Numeric) || volume_id.is_a?(String)
137
+ url = "#{@api_url}/#{id}/volumes/#{volume_id}"
138
+ else
139
+ raise "passed a bad volume_id: #{volume_id || '(none)'}" # lazy
140
+ end
141
+ headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
142
+ execute(method: :delete, url: url, headers: headers, payload: payload.to_json)
143
+ end
144
+
145
+ alias :delete_volume :destroy_volume
146
+
147
+ def list_namespaces(id, params={})
148
+ url = "#{@api_url}/#{id}/namespaces"
149
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
150
+ execute(method: :get, url: url, headers: headers)
151
+ end
152
+
153
+ def get_namespace(id, namespace_id, params={})
154
+ url = "#{@api_url}/#{id}/namespaces/#{namespace_id}"
155
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
156
+ execute(method: :get, url: url, headers: headers)
157
+ end
158
+
159
+ def create_namespace(id, payload)
160
+ url = "#{@api_url}/#{id}/namespaces"
161
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
162
+ execute(method: :post, url: url, headers: headers, payload: payload.to_json)
163
+ end
164
+
165
+ def update_namespace(id, namespace_id, payload)
166
+ url = "#{@api_url}/#{id}/namespaces/#{namespace_id}"
167
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
168
+ execute(method: :put, url: url, headers: headers, payload: payload.to_json)
169
+ end
170
+
171
+ def destroy_namespace(id, namespace_id, params={})
172
+ url = "#{@api_url}/#{id}/namespaces/#{namespace_id}"
173
+ headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
174
+ execute(method: :delete, url: url, headers: headers)
175
+ end
176
+
177
+ alias :delete_namespace :destroy_namespace
178
+
179
+ def list_containers(id, params={})
180
+ url = "#{@api_url}/#{id}/containers"
181
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
182
+ execute(method: :get, url: url, headers: headers)
183
+ end
184
+
185
+ def restart_container(id, container_id, params={})
186
+ url = "#{@api_url}/#{id}/containers/#{container_id}/restart"
187
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
188
+ execute(method: :put, url: url, headers: headers)
189
+ end
190
+
191
+ def destroy_container(id, container_id, params={})
192
+ if container_id.is_a?(Array)
193
+ url = "#{@api_url}/#{id}/containers"
194
+ params['containerId'] = container_id
195
+ elsif container_id.is_a?(Numeric) || container_id.is_a?(String)
196
+ url = "#{@api_url}/#{id}/containers/#{container_id}"
197
+ else
198
+ raise "passed a bad container_id: #{container_id || '(none)'}" # lazy
199
+ end
200
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
201
+ execute(method: :delete, url: url, headers: headers)
202
+ end
203
+
204
+ def list_container_groups(id, resource_type, params={})
205
+ url = "#{@api_url}/#{id}/#{resource_type}s"
206
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
207
+ execute(method: :get, url: url, headers: headers)
208
+ end
209
+
210
+ def restart_container_group(id, container_group_id, resource_type, params={})
211
+ url = "#{@api_url}/#{id}/#{resource_type}s/#{container_group_id}/restart"
212
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
213
+ execute(method: :put, url: url, headers: headers)
214
+ end
215
+
216
+ def destroy_container_group(id, container_group_id, resource_type, params={})
217
+ if container_group_id.is_a?(Array)
218
+ url = "#{@api_url}/#{id}/#{resource_type}s"
219
+ params['containerGroupId'] = container_group_id
220
+ elsif container_group_id.is_a?(Numeric) || container_group_id.is_a?(String)
221
+ url = "#{@api_url}/#{id}/#{resource_type}s/#{container_group_id}"
222
+ else
223
+ raise "passed a bad container_group_id: #{container_group_id || '(none)'}" # lazy
224
+ end
225
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
226
+ execute(method: :delete, url: url, headers: headers)
227
+ end
228
+
229
+ # def list_pods(id, params={})
230
+ # url = "#{@api_url}/#{id}/pods"
231
+ # headers = { params: params, authorization: "Bearer #{@access_token}" }
232
+ # execute(method: :get, url: url, headers: headers)
233
+ # end
234
+
235
+ def wiki(id, params)
236
+ url = "#{@base_url}/api/clusters/#{id}/wiki"
237
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
238
+ opts = {method: :get, url: url, headers: headers}
239
+ execute(opts)
240
+ end
241
+
242
+ def update_wiki(id, payload)
243
+ url = "#{@base_url}/api/clusters/#{id}/wiki"
244
+ headers = {authorization: "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
245
+ opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
246
+ execute(opts)
247
+ end
248
+
249
+ def api_config(id, params={})
250
+ url = "#{@api_url}/#{id}/api-config"
251
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
252
+ execute(method: :get, url: url, headers: headers)
253
+ end
254
+
255
+ def history(id, params={})
256
+ url = "#{@base_url}/api/clusters/#{id}/history"
257
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
258
+ opts = {method: :get, url: url, headers: headers}
259
+ execute(opts)
260
+ end
261
+
262
+ def history_details(id, process_id, params={})
263
+ url = "#{@base_url}/api/clusters/#{id}/history/#{process_id}"
264
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
265
+ opts = {method: :get, url: url, headers: headers}
266
+ execute(opts)
267
+ end
268
+
269
+ def history_event_details(id, process_event_id, params={})
270
+ url = "#{@base_url}/api/clusters/#{id}/history/events/#{process_event_id}"
271
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
272
+ opts = {method: :get, url: url, headers: headers}
273
+ execute(opts)
274
+ end
275
+
276
+ end
@@ -0,0 +1,26 @@
1
+ require 'morpheus/api/api_client'
2
+
3
+ class Morpheus::LibraryComputeTypeLayoutsInterface < Morpheus::APIClient
4
+ def initialize(access_token, refresh_token,expires_at = nil, base_url=nil)
5
+ @access_token = access_token
6
+ @refresh_token = refresh_token
7
+ @base_url = base_url
8
+ @expires_at = expires_at
9
+ end
10
+
11
+ def list(params={})
12
+ url = "#{@base_url}/api/library/compute-type-layouts"
13
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
14
+ opts = {method: :get, url: url, headers: headers}
15
+ execute(opts)
16
+ end
17
+
18
+ def get(id, params={})
19
+ raise "#{self.class}.get() passed a blank id!" if id.to_s == ''
20
+ url = "#{@base_url}/api/library/compute-type-layouts/#{id}"
21
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
22
+ opts = {method: :get, url: url, headers: headers}
23
+ execute(opts)
24
+ end
25
+
26
+ end
@@ -20,6 +20,12 @@ class Morpheus::LogsInterface < Morpheus::APIClient
20
20
  execute({method: :get, url: url, headers: headers})
21
21
  end
22
22
 
23
+ def cluster_logs(id, params={})
24
+ url = "#{@base_url}/api/logs"
25
+ headers = { params: {'clusterId' => id}.merge(params), authorization: "Bearer #{@access_token}" }
26
+ execute({method: :get, url: url, headers: headers})
27
+ end
28
+
23
29
  def stats()
24
30
  url = "#{@base_url}/api/logs/log-stats"
25
31
  headers = { params: {}, authorization: "Bearer #{@access_token}" }
@@ -0,0 +1,26 @@
1
+ require 'morpheus/api/api_client'
2
+
3
+ class Morpheus::NetworkSubnetTypesInterface < Morpheus::APIClient
4
+ def initialize(access_token, refresh_token,expires_at = nil, base_url=nil)
5
+ @access_token = access_token
6
+ @refresh_token = refresh_token
7
+ @base_url = base_url
8
+ @expires_at = expires_at
9
+ end
10
+
11
+ def get(id, params={})
12
+ raise "#{self.class}.get() passed a blank id!" if id.to_s == ''
13
+ url = "#{@base_url}/api/network-subnet-types/#{id}"
14
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
15
+ opts = {method: :get, url: url, headers: headers}
16
+ execute(opts)
17
+ end
18
+
19
+ def list(params={})
20
+ url = "#{@base_url}/api/network-subnet-types"
21
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
22
+ opts = {method: :get, url: url, headers: headers}
23
+ execute(opts)
24
+ end
25
+
26
+ end