enfcli 4.0.0 → 5.0.0.pre.alpha
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.
- checksums.yaml +4 -4
- data/.circleci/Dockerfile +2 -2
- data/.circleci/config.yml +5 -0
- data/Gemfile.lock +38 -26
- data/Makefile +7 -0
- data/README.md +52 -7
- data/enfcli.gemspec +28 -26
- data/format.sh +9 -0
- data/lib/enfapi.rb +184 -237
- data/lib/enfapi/dns.rb +95 -0
- data/lib/enfapi/firewall.rb +37 -0
- data/lib/enfapi/user.rb +75 -0
- data/lib/enfcli.rb +211 -111
- data/lib/enfcli/commands/captive.rb +518 -157
- data/lib/enfcli/commands/user.rb +208 -160
- data/lib/enfcli/commands/xcr.rb +151 -119
- data/lib/enfcli/commands/xdns.rb +65 -55
- data/lib/enfcli/commands/xfw.rb +37 -37
- data/lib/enfcli/commands/xiam.rb +87 -80
- data/lib/enfcli/version.rb +2 -2
- data/lib/enfthor.rb +38 -14
- metadata +65 -5
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2018-
|
4
|
+
# Copyright 2018-2020 Xaptum,Inc
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -15,21 +15,22 @@
|
|
15
15
|
# See the License for the specific language governing permissions and
|
16
16
|
# limitations under the License.
|
17
17
|
#
|
18
|
-
require
|
19
|
-
require
|
20
|
-
require
|
21
|
-
require
|
18
|
+
require "enfthor"
|
19
|
+
require "enfapi"
|
20
|
+
require "json"
|
21
|
+
require "erb"
|
22
22
|
|
23
23
|
module EnfCli
|
24
24
|
module Cmd
|
25
25
|
class Captive < EnfThor
|
26
|
-
desc
|
27
|
-
|
28
|
-
method_option :name, default: nil, type: :string, banner:
|
29
|
-
desc:
|
30
|
-
method_option :domain, default: nil, type: :string, banner:
|
31
|
-
aliases:
|
32
|
-
method_option :ssid, default: nil, type: :string, banner:
|
26
|
+
desc "list-wifi-configurations",
|
27
|
+
"List wifi configuration information for all or the matching records."
|
28
|
+
method_option :name, default: nil, type: :string, banner: "NAME",
|
29
|
+
desc: "where NAME will match the user-given name."
|
30
|
+
method_option :domain, default: nil, type: :string, banner: "DOMAIN",
|
31
|
+
aliases: "-d"
|
32
|
+
method_option :ssid, default: nil, type: :string, banner: "SSID"
|
33
|
+
|
33
34
|
def list_wifi_configurations
|
34
35
|
try_with_rescue_in_session do
|
35
36
|
## TODO: V1 is only listing all of the wifi configurations, it is not
|
@@ -40,15 +41,16 @@ module EnfCli
|
|
40
41
|
# display the data
|
41
42
|
display_wifi_configs wifi_configs
|
42
43
|
else
|
43
|
-
say
|
44
|
+
say "No WiFi configurations found."
|
44
45
|
end
|
45
46
|
end
|
46
47
|
end
|
47
48
|
|
48
|
-
desc
|
49
|
+
desc "create-wifi-configuration", "Create a new wifi configuration."
|
49
50
|
method_option :'wifi-config-file', type: :string, required: true,
|
50
|
-
banner:
|
51
|
-
desc:
|
51
|
+
banner: "<file>",
|
52
|
+
desc: "<file> is JSON file with parameters required for configuring the router card."
|
53
|
+
|
52
54
|
def create_wifi_configuration
|
53
55
|
try_with_rescue_in_session do
|
54
56
|
json_file_name = options[:'wifi-config-file']
|
@@ -65,20 +67,21 @@ module EnfCli
|
|
65
67
|
end
|
66
68
|
end
|
67
69
|
|
68
|
-
desc
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
method_option :'wifi-id', defaut: nil, type: :string, banner:
|
73
|
-
desc:
|
74
|
-
method_option :version, defaut: nil, type: :string, banner:
|
75
|
-
desc:
|
76
|
-
method_option :'profile-id', defaut: nil, type: :string, banner:
|
77
|
-
desc:
|
78
|
-
method_option :'device-id', defaut: nil, type: :string, banner:
|
79
|
-
desc:
|
80
|
-
method_option :'wifi-name', defaut: nil, type: :string, banner:
|
81
|
-
desc:
|
70
|
+
desc "get-wifi-configuration",
|
71
|
+
"Get details of the specified wifi configuration. Exactly one of: " \
|
72
|
+
"--wifi-id, --profile-id, --device-id, or --wifi-name must be " \
|
73
|
+
"specified."
|
74
|
+
method_option :'wifi-id', defaut: nil, type: :string, banner: "WIFI-ID",
|
75
|
+
desc: "WIFI-ID is the UUID of the wifi record"
|
76
|
+
method_option :version, defaut: nil, type: :string, banner: "VERSION",
|
77
|
+
desc: "Optionally used with --wifi-id"
|
78
|
+
method_option :'profile-id', defaut: nil, type: :string, banner: "PROFILE-ID",
|
79
|
+
desc: "PROFILE-ID is the UUID of the profile."
|
80
|
+
method_option :'device-id', defaut: nil, type: :string, banner: "DEVICE-ID",
|
81
|
+
desc: "DEVICE-ID is the UUID of the device."
|
82
|
+
method_option :'wifi-name', defaut: nil, type: :string, banner: "WIFI-NAME",
|
83
|
+
desc: "Result matches any wifi configuration that contains the substring WIFI-NAME"
|
84
|
+
|
82
85
|
def get_wifi_configuration
|
83
86
|
try_with_rescue_in_session do
|
84
87
|
## TODO - Currently, this only handles wifi-id, add the others to v2
|
@@ -95,31 +98,30 @@ module EnfCli
|
|
95
98
|
num_query_opts += 1 if device_id
|
96
99
|
num_query_opts += 1 if wifi_name
|
97
100
|
|
98
|
-
|
99
|
-
raise 'ERROR: Exactly one of: --wifi-id, --profile-id, --device-id, or --wifi-name must be specified.' if num_query_opts != 1
|
101
|
+
raise "ERROR: Exactly one of: --wifi-id, --profile-id, --device-id, or --wifi-name must be specified." if num_query_opts != 1
|
100
102
|
|
101
103
|
if wifi_id
|
102
104
|
wifi_config = EnfApi::Captive.instance.get_wifi_configuration wifi_id, version
|
103
105
|
|
104
106
|
# display the data
|
105
107
|
display_wifi_detail wifi_config
|
106
|
-
|
107
108
|
end
|
108
109
|
end
|
109
110
|
end
|
110
111
|
|
111
|
-
desc
|
112
|
-
|
112
|
+
desc "update-wifi-configuration",
|
113
|
+
"Update an existing wifi configuration."
|
113
114
|
method_option :'wifi-id', type: :string,
|
114
115
|
required: true,
|
115
|
-
banner:
|
116
|
-
desc:
|
116
|
+
banner: "WIFI-ID",
|
117
|
+
desc: "WIFI-ID is the UUID of the wifi profile"
|
117
118
|
method_option :'wifi-config-file', type: :string,
|
118
119
|
required: true,
|
119
|
-
banner:
|
120
|
-
desc:
|
121
|
-
|
122
|
-
|
120
|
+
banner: "<file>",
|
121
|
+
desc: "<file> is JSON file with " \
|
122
|
+
"parameters required for " \
|
123
|
+
"configuring the router card."
|
124
|
+
|
123
125
|
def update_wifi_configuration
|
124
126
|
try_with_rescue_in_session do
|
125
127
|
json_file_name = options[:'wifi-config-file']
|
@@ -133,29 +135,29 @@ module EnfCli
|
|
133
135
|
end
|
134
136
|
end
|
135
137
|
|
138
|
+
desc "create-device",
|
139
|
+
"Add a new device to the database. This is only available to Xaptum administrators"
|
140
|
+
method_option :'device-id', type: :string, required: true, banner: "SERIAL-NUM",
|
141
|
+
desc: "SERIAL-NUM is the serial number of the device."
|
142
|
+
method_option :'device-name', type: :string, default: nil, banner: "DEVICE-NAME",
|
143
|
+
desc: "User-defined name for the device."
|
144
|
+
method_option :'mac-addr1', type: :string, default: nil, banner: "MAC-ADDR1",
|
145
|
+
desc: "MAC address 1 (wifi address on a wifi-enabled device)"
|
146
|
+
method_option :'mac-addr2', type: :string, default: nil, banner: "MAC-ADDR2",
|
147
|
+
desc: "MAC address 2"
|
148
|
+
method_option :'mac-addr3', type: :string, default: nil, banner: "MAC-ADDR3",
|
149
|
+
desc: "MAC address 3"
|
150
|
+
method_option :'mac-addr4', type: :string, default: nil, banner: "MAC-ADDR4",
|
151
|
+
desc: "MAC address 4"
|
152
|
+
method_option :'profile-id', type: :string, default: nil, banner: "PROFILE_ID",
|
153
|
+
desc: "UUID of the profile that the device will use. The profile must already exist."
|
154
|
+
method_option :model, type: :string, default: nil, banner: "MODEL",
|
155
|
+
desc: "Model identifier of the device"
|
136
156
|
|
137
|
-
desc 'create-device',
|
138
|
-
'Add a new device to the database. This is only available to Xaptum administrators'
|
139
|
-
method_option :'device-id', type: :string, required: true, banner: 'SERIAL-NUM',
|
140
|
-
desc: 'SERIAL-NUM is the serial number of the device.'
|
141
|
-
method_option :'device-name', type: :string, default: nil, banner: 'DEVICE-NAME',
|
142
|
-
desc: 'User-defined name for the device.'
|
143
|
-
method_option :'mac-addr1', type: :string, default: nil, banner: 'MAC-ADDR1',
|
144
|
-
desc: 'MAC address 1 (wifi address on a wifi-enabled device)'
|
145
|
-
method_option :'mac-addr2', type: :string, default: nil, banner: 'MAC-ADDR2',
|
146
|
-
desc: 'MAC address 2'
|
147
|
-
method_option :'mac-addr3', type: :string, default: nil, banner: 'MAC-ADDR3',
|
148
|
-
desc: 'MAC address 3'
|
149
|
-
method_option :'mac-addr4', type: :string, default: nil, banner: 'MAC-ADDR4',
|
150
|
-
desc: 'MAC address 4'
|
151
|
-
method_option :'profile-id', type: :string, default: nil, banner: 'PROFILE_ID',
|
152
|
-
desc: 'UUID of the profile that the device will use. The profile must already exist.'
|
153
|
-
method_option :model, type: :string, default: nil, banner: 'MODEL',
|
154
|
-
desc: 'Model identifier of the device'
|
155
157
|
def create_device
|
156
158
|
try_with_rescue_in_session do
|
157
159
|
new_device_hash = {
|
158
|
-
serial_number: options[:'device-id']
|
160
|
+
serial_number: options[:'device-id'],
|
159
161
|
}
|
160
162
|
|
161
163
|
mac1 = options[:'mac-addr1']
|
@@ -192,10 +194,11 @@ module EnfCli
|
|
192
194
|
end
|
193
195
|
end
|
194
196
|
|
195
|
-
desc
|
196
|
-
|
197
|
-
method_option :network, default: nil, type: :string, banner:
|
198
|
-
desc:
|
197
|
+
desc "list-devices",
|
198
|
+
"List basic device information for all devices matching the option specified."
|
199
|
+
method_option :network, default: nil, type: :string, banner: "NETWORK",
|
200
|
+
desc: "NETWORK is the ipv6 subnet used by the device."
|
201
|
+
|
199
202
|
def list_devices
|
200
203
|
try_with_rescue_in_session do
|
201
204
|
network = options[:network]
|
@@ -205,11 +208,12 @@ module EnfCli
|
|
205
208
|
end
|
206
209
|
end
|
207
210
|
|
208
|
-
desc
|
209
|
-
|
211
|
+
desc "get-device",
|
212
|
+
"Get details of the specified device."
|
210
213
|
method_option :'device-id', required: true, type: :string,
|
211
|
-
banner:
|
212
|
-
desc:
|
214
|
+
banner: "DEVICE-ID",
|
215
|
+
desc: "DEVICE-ID is either the device serial number or its ipv6 address."
|
216
|
+
|
213
217
|
def get_device
|
214
218
|
try_with_rescue_in_session do
|
215
219
|
device_id = ERB::Util::url_encode(options[:'device-id'])
|
@@ -220,14 +224,15 @@ module EnfCli
|
|
220
224
|
end
|
221
225
|
end
|
222
226
|
|
223
|
-
desc
|
224
|
-
|
225
|
-
method_option :'device-id', type: :string, required: true, banner:
|
226
|
-
desc:
|
227
|
-
method_option :'device-name', type: :string, default: nil, banner:
|
228
|
-
|
229
|
-
method_option :'profile-id', type: :string, default: nil, banner:
|
230
|
-
|
227
|
+
desc "update-device",
|
228
|
+
"Update an existing device record with the values specified."
|
229
|
+
method_option :'device-id', type: :string, required: true, banner: "DEVICE-ID",
|
230
|
+
desc: "DEVICE-ID is either the device serial number or its ipv6 address."
|
231
|
+
method_option :'device-name', type: :string, default: nil, banner: "DEVICE-NAME",
|
232
|
+
desc: "User-defined name for the device."
|
233
|
+
method_option :'profile-id', type: :string, default: nil, banner: "PROFILE_ID",
|
234
|
+
desc: "UUID of the profile that the device will use. The profile must already exist."
|
235
|
+
|
231
236
|
def update_device
|
232
237
|
try_with_rescue_in_session do
|
233
238
|
id = ERB::Util::url_encode(options[:'device-id'])
|
@@ -248,11 +253,12 @@ module EnfCli
|
|
248
253
|
end
|
249
254
|
end
|
250
255
|
|
251
|
-
desc
|
252
|
-
|
256
|
+
desc "get-device-status",
|
257
|
+
"Get the latest status of the specified device."
|
253
258
|
method_option :'device-id', required: true, type: :string,
|
254
|
-
banner:
|
255
|
-
desc:
|
259
|
+
banner: "DEVICE-ID",
|
260
|
+
desc: "DEVICE-ID is either the device serial number or its ipv6 address."
|
261
|
+
|
256
262
|
def get_device_status
|
257
263
|
try_with_rescue_in_session do
|
258
264
|
device_id = ERB::Util::url_encode(options[:'device-id'])
|
@@ -262,22 +268,27 @@ module EnfCli
|
|
262
268
|
end
|
263
269
|
end
|
264
270
|
|
265
|
-
desc
|
266
|
-
|
271
|
+
desc "create-profile",
|
272
|
+
"Create a new profile."
|
267
273
|
method_option :'profile-name', type: :string, required: true,
|
268
|
-
banner:
|
269
|
-
desc:
|
274
|
+
banner: "PROFILE-NAME",
|
275
|
+
desc: "PROFILE-NAME is the user-given name."
|
270
276
|
method_option :'device-mode', type: :string, required: true,
|
271
|
-
banner:
|
272
|
-
desc:
|
273
|
-
method_option :'wifi-id', type: :string, default: nil, banner:
|
274
|
-
desc:
|
277
|
+
banner: "DEVICE-MODE",
|
278
|
+
desc: "secure-host or passthrough"
|
279
|
+
method_option :'wifi-id', type: :string, default: nil, banner: "WIFI-ID",
|
280
|
+
desc: "WIFI-ID is the UUID of the wifi record that the profile will use. wifi record must already exist"
|
281
|
+
method_option :'update-id', type: :string, default: nil,
|
282
|
+
banner: "UPDATE-ID",
|
283
|
+
desc: "UPDATE-ID is the UUID of the firmware update record. " \
|
284
|
+
"The firmware-update record must already exist."
|
285
|
+
|
275
286
|
def create_profile
|
276
287
|
try_with_rescue_in_session do
|
277
288
|
profile_name = options[:'profile-name']
|
278
289
|
new_profile_hash = {
|
279
290
|
name: profile_name,
|
280
|
-
config: { mode: options[:'device-mode'] }
|
291
|
+
config: { mode: options[:'device-mode'] },
|
281
292
|
}
|
282
293
|
|
283
294
|
wifi = options[:'wifi-id']
|
@@ -286,16 +297,23 @@ module EnfCli
|
|
286
297
|
new_profile_hash[:config][:wifi][:id] = wifi
|
287
298
|
end
|
288
299
|
|
300
|
+
fw_update = options[:'update-id']
|
301
|
+
if fw_update
|
302
|
+
new_profile_hash[:config][:firmware] = {}
|
303
|
+
new_profile_hash[:config][:firmware][:id] = fw_update if fw_update
|
304
|
+
end
|
305
|
+
|
289
306
|
# send the POST to create a new profile
|
290
307
|
profile = EnfApi::Captive.instance.create_profile new_profile_hash
|
291
308
|
display_profile profile
|
292
309
|
end
|
293
310
|
end
|
294
311
|
|
295
|
-
desc
|
296
|
-
|
297
|
-
method_option :name, default: nil, type: :string, banner:
|
298
|
-
desc:
|
312
|
+
desc "list-profiles",
|
313
|
+
"List the existing profiles."
|
314
|
+
method_option :name, default: nil, type: :string, banner: "NAME",
|
315
|
+
desc: "where NAME will match all or part of the user-given profile name."
|
316
|
+
|
299
317
|
def list_profiles
|
300
318
|
try_with_rescue_in_session do
|
301
319
|
query_name = options[:name]
|
@@ -304,14 +322,14 @@ module EnfCli
|
|
304
322
|
end
|
305
323
|
end
|
306
324
|
|
307
|
-
desc
|
308
|
-
|
309
|
-
method_option :'profile-id', required: true, type: :string,
|
310
|
-
banner:
|
311
|
-
desc:
|
312
|
-
# TODO - server doesn't support version yet.
|
313
|
-
# method_option :version, default: nil, type: :integer, banner: 'VERSION',
|
314
|
-
# desc: 'Get a specific version.'
|
325
|
+
desc "get-profile",
|
326
|
+
"Get full detail listing of the profile."
|
327
|
+
method_option :'profile-id', required: true, type: :string,
|
328
|
+
banner: "PROFILE-ID",
|
329
|
+
desc: "PROFILE-ID is the UUID of the profile."
|
330
|
+
# TODO - server doesn't support version yet.
|
331
|
+
# method_option :version, default: nil, type: :integer, banner: 'VERSION',
|
332
|
+
# desc: 'Get a specific version.'
|
315
333
|
def get_profile
|
316
334
|
try_with_rescue_in_session do
|
317
335
|
profile = EnfApi::Captive.instance.get_profile options[:'profile-id']
|
@@ -319,44 +337,274 @@ module EnfCli
|
|
319
337
|
end
|
320
338
|
end
|
321
339
|
|
322
|
-
desc
|
323
|
-
|
324
|
-
|
325
|
-
method_option :'profile-id', required: true, type: :string,
|
326
|
-
banner:
|
327
|
-
desc:
|
340
|
+
desc "update-profile",
|
341
|
+
"Update a previously-created profile specified by PROFILE-ID. At " \
|
342
|
+
"least one property must be changed."
|
343
|
+
method_option :'profile-id', required: true, type: :string,
|
344
|
+
banner: "PROFILE-ID",
|
345
|
+
desc: "UUID of the device profile."
|
328
346
|
method_option :'profile-name', type: :string, default: nil,
|
329
|
-
banner:
|
330
|
-
desc:
|
347
|
+
banner: "PROFILE-NAME",
|
348
|
+
desc: "PROFILE-NAME is the user-given name."
|
331
349
|
method_option :'device-mode', type: :string, default: nil,
|
332
|
-
banner:
|
333
|
-
desc:
|
334
|
-
method_option :'wifi-id', type: :string, default: nil, banner:
|
335
|
-
desc:
|
350
|
+
banner: "DEVICE-MODE",
|
351
|
+
desc: "secure-host or passthrough"
|
352
|
+
method_option :'wifi-id', type: :string, default: nil, banner: "WIFI-ID",
|
353
|
+
desc: "WIFI-ID is the UUID of the wifi record that the profile will use. wifi record must already exist"
|
354
|
+
method_option :'update-id', type: :string, default: nil,
|
355
|
+
banner: "UPDATE-ID",
|
356
|
+
desc: "UPDATE-ID is the UUID of the firmware update record. " \
|
357
|
+
"The firmware-update record must already exist."
|
358
|
+
|
336
359
|
def update_profile
|
337
360
|
try_with_rescue_in_session do
|
338
361
|
id = options[:'profile-id']
|
339
362
|
name = options[:'profile-name']
|
340
363
|
mode = options[:'device-mode']
|
341
364
|
wifi_id = options[:'wifi-id']
|
365
|
+
fw_update = options[:'update-id']
|
342
366
|
|
343
|
-
raise "At least one option needs to change." if name == nil && mode == nil && wifi_id == nil
|
367
|
+
raise "At least one option needs to change." if name == nil && mode == nil && wifi_id == nil && fw_update == nil
|
344
368
|
|
345
369
|
update_hash = {}
|
346
370
|
update_hash[:name] = name if name
|
347
|
-
update_hash[:config] = {} if wifi_id || mode
|
371
|
+
update_hash[:config] = {} if wifi_id || mode || fw_update
|
348
372
|
update_hash[:config][:mode] = mode if mode
|
349
373
|
if wifi_id
|
350
374
|
update_hash[:config][:wifi] = {}
|
351
375
|
update_hash[:config][:wifi][:id] = wifi_id
|
352
376
|
end
|
353
377
|
|
378
|
+
if fw_update
|
379
|
+
update_hash[:config][:firmware] = {}
|
380
|
+
update_hash[:config][:firmware][:id] = fw_update
|
381
|
+
end
|
354
382
|
profile = EnfApi::Captive.instance.update_profile id, update_hash
|
355
383
|
display_profile profile
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
desc "list-firmware-images",
|
388
|
+
"Lists all of the available versions of the firmware"
|
389
|
+
|
390
|
+
def list_firmware_images
|
391
|
+
try_with_rescue_in_session do
|
392
|
+
data = EnfApi::Captive.instance.list_firmware_images
|
393
|
+
display_firmware_image_list data
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
desc "upload-firmware-image",
|
398
|
+
"Uploads a new firmware image. Each version will have multiple " \
|
399
|
+
"images -- one for each type of hardware. \nNOTE: This version " \
|
400
|
+
"doesn't actually upload the file, it merely informs the server of " \
|
401
|
+
"its existance."
|
402
|
+
method_option :'image-file', required: true, type: :string,
|
403
|
+
banner: "<file>", desc: "<file> is the firmware binary image."
|
404
|
+
method_option :version, required: true, type: :string,
|
405
|
+
banner: "VERSION",
|
406
|
+
desc: "VERSION is the release version."
|
407
|
+
method_option :'image-name', type: :string, default: nil, banner: "NAME",
|
408
|
+
desc: "NAME is the name to associate with the firmware " \
|
409
|
+
"image. This is usually the generated filename. " \
|
410
|
+
"Defaults to the base name of <file>"
|
411
|
+
|
412
|
+
def upload_firmware_image
|
413
|
+
try_with_rescue_in_session do
|
414
|
+
filename = options[:'image-file']
|
415
|
+
version = options[:version]
|
416
|
+
image_name = options[:'image-name']
|
417
|
+
|
418
|
+
temp_img_name = File.basename(filename)
|
419
|
+
image_name ||= temp_img_name
|
420
|
+
|
421
|
+
raise "image-name does not match image-file" if temp_img_name != image_name
|
422
|
+
|
423
|
+
# This version PUTs an empty body
|
424
|
+
resp = EnfApi::Captive.instance.upload_firmware_image version, image_name
|
425
|
+
|
426
|
+
if (resp.code == 200)
|
427
|
+
say "Upload complete."
|
428
|
+
else
|
429
|
+
say "Upload failed with code #{resp.code}"
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
desc "get-firmware-info",
|
435
|
+
"Prints details of an existing firmware version."
|
436
|
+
method_option :version, required: true, type: :string,
|
437
|
+
banner: "VERSION", desc: "VERSION is the release version."
|
438
|
+
|
439
|
+
def get_firmware_info
|
440
|
+
try_with_rescue_in_session do
|
441
|
+
version = options[:version]
|
356
442
|
|
443
|
+
fw_info = EnfApi::Captive.instance.get_firmware_info version
|
444
|
+
|
445
|
+
display_firmware_detail fw_info
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
desc "create-schedule",
|
450
|
+
"Creates a schedule object that will be used to determine when a " \
|
451
|
+
"device or group of devices may be upgraded. The schedule will be " \
|
452
|
+
"uploaded from a JSON-formatted file."
|
453
|
+
method_option :schedule, required: true, type: :string,
|
454
|
+
banner: "<file>",
|
455
|
+
desc: "<file> is a JSON file containing the desired schedule."
|
456
|
+
|
457
|
+
def create_schedule
|
458
|
+
try_with_rescue_in_session do
|
459
|
+
filename = options[:schedule]
|
460
|
+
|
461
|
+
# reading the whole file - shouldn't get more than a few KB
|
462
|
+
content = File.read filename
|
463
|
+
sched_hash = JSON.parse(content)
|
464
|
+
|
465
|
+
resp_data = EnfApi::Captive.instance.create_schedule sched_hash
|
466
|
+
display_schedule_detail resp_data
|
357
467
|
end
|
358
468
|
end
|
359
469
|
|
470
|
+
desc "list-schedules",
|
471
|
+
"Prints a summary list of all existing schedules."
|
472
|
+
|
473
|
+
def list_schedules
|
474
|
+
try_with_rescue_in_session do
|
475
|
+
sched_list = EnfApi::Captive.instance.list_schedules
|
476
|
+
display_schedule_list sched_list
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
desc "get-schedule",
|
481
|
+
"Prints the detail of a specific, existing schedule."
|
482
|
+
method_option :'schedule-id', type: :string, required: true, banner: "ID",
|
483
|
+
desc: "ID is the system-assigned UUID of the schedule " \
|
484
|
+
"to retrieve."
|
485
|
+
|
486
|
+
def get_schedule
|
487
|
+
try_with_rescue_in_session do
|
488
|
+
sched_id = options[:'schedule-id']
|
489
|
+
sched_data = EnfApi::Captive.instance.get_schedule sched_id
|
490
|
+
display_schedule_detail sched_data
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
desc "update-schedule",
|
495
|
+
"Modifies an existing schedule by uploading an updated schedule in " \
|
496
|
+
"the form of a JSON file."
|
497
|
+
method_option :'schedule-id', type: :string, required: true, banner: "ID",
|
498
|
+
desc: "ID is the UUID of the schedule to update."
|
499
|
+
method_option :schedule, type: :string, required: true, bannder: "<file>",
|
500
|
+
desc: "<file> is a JSON file containing the desired schedule."
|
501
|
+
|
502
|
+
def update_schedule
|
503
|
+
try_with_rescue_in_session do
|
504
|
+
sched_id = options[:'schedule-id']
|
505
|
+
filename = options[:schedule]
|
506
|
+
|
507
|
+
# read in whole schedule file
|
508
|
+
content = File.read filename
|
509
|
+
sched_hash = JSON.parse(content)
|
510
|
+
|
511
|
+
resp_data = EnfApi::Captive.instance.update_schedule sched_id, sched_hash
|
512
|
+
display_schedule_detail resp_data
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
desc "delete-schedule",
|
517
|
+
"Deletes the specified, existing schedule."
|
518
|
+
method_option :'schedule-id', type: :string, required: true, banner: "ID",
|
519
|
+
desc: "ID is the system-assigned UUID of the schedule " \
|
520
|
+
"to delete."
|
521
|
+
|
522
|
+
def delete_schedule
|
523
|
+
try_with_rescue_in_session do
|
524
|
+
sched_id = options[:'schedule-id']
|
525
|
+
|
526
|
+
# get the name of the schedule
|
527
|
+
resp = EnfApi::Captive.instance.get_schedule(sched_id)
|
528
|
+
name = resp[:name]
|
529
|
+
|
530
|
+
resp = EnfApi::Captive.instance.delete_schedule sched_id
|
531
|
+
|
532
|
+
if (resp.code == 200)
|
533
|
+
say "Successfully deleted schedule named: #{name}"
|
534
|
+
elsif (resp.code == 409)
|
535
|
+
say "Schedule #{name} is being used by pending updates and cannot be deleted."
|
536
|
+
else
|
537
|
+
say "Failed to delete schedule #{name} with code #{resp.code}"
|
538
|
+
end
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
desc "list-firmware-updates",
|
543
|
+
"Lists the existing firmware update tasks."
|
544
|
+
|
545
|
+
def list_firmware_updates
|
546
|
+
try_with_rescue_in_session do
|
547
|
+
updates_list = EnfApi::Captive.instance.list_firmware_updates
|
548
|
+
display_updates_list updates_list
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
desc "get-firmware-update",
|
553
|
+
"Prints the details of the specified firmware update task."
|
554
|
+
method_option :'update-id', type: :string, required: true, banner: "ID",
|
555
|
+
desc: "ID is the UUID of the firmware update task"
|
556
|
+
|
557
|
+
def get_firmware_update
|
558
|
+
try_with_rescue_in_session do
|
559
|
+
update_id = options[:'update-id']
|
560
|
+
|
561
|
+
update_data = EnfApi::Captive.instance.get_firmware_update update_id
|
562
|
+
display_update_detail update_data
|
563
|
+
end
|
564
|
+
end
|
565
|
+
|
566
|
+
desc "create-firmware-update",
|
567
|
+
"Creates a firmware-update task that the system will use to update " \
|
568
|
+
"router-card firmware as prescribed in the specified JSON file."
|
569
|
+
method_option :update, type: :string, required: true, banner: "<file>",
|
570
|
+
desc: "<file> is a JSON file containing the details of " \
|
571
|
+
"the update task."
|
572
|
+
|
573
|
+
def create_firmware_update
|
574
|
+
try_with_rescue_in_session do
|
575
|
+
filename = options[:update]
|
576
|
+
|
577
|
+
# reading the whole file - shouldn't get more than a few KB
|
578
|
+
content = File.read filename
|
579
|
+
update_hash = JSON.parse(content)
|
580
|
+
|
581
|
+
resp_data = EnfApi::Captive.instance.create_firmware_update update_hash
|
582
|
+
display_update_detail resp_data
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
desc "modify-firmware-update",
|
587
|
+
"Modifies an existing firmware-update task with the information " \
|
588
|
+
"contained in the specified JSON file"
|
589
|
+
method_option :'update-id', type: :string, required: true, banner: "ID",
|
590
|
+
desc: "ID is the UUID of the firmware update task to be modified."
|
591
|
+
method_option :update, type: :string, required: true, banner: "<file>",
|
592
|
+
desc: "<file> is a JSON file containing the details of " \
|
593
|
+
"the update task."
|
594
|
+
|
595
|
+
def modify_firmware_update
|
596
|
+
try_with_rescue_in_session do
|
597
|
+
update_id = options[:'update-id']
|
598
|
+
filename = options[:update]
|
599
|
+
|
600
|
+
# reading the whole file - shouldn't get more than a few KB
|
601
|
+
content = File.read filename
|
602
|
+
update_hash = JSON.parse(content)
|
603
|
+
|
604
|
+
resp_data = EnfApi::Captive.instance.modify_firmware_update update_id, update_hash
|
605
|
+
display_update_detail resp_data
|
606
|
+
end
|
607
|
+
end
|
360
608
|
|
361
609
|
#########################################################################
|
362
610
|
#
|
@@ -367,7 +615,7 @@ module EnfCli
|
|
367
615
|
# Displays the wifi configuration summary list.
|
368
616
|
# TODO - Does non-Xaptum-admin columns only - add option to do admin list
|
369
617
|
def display_wifi_configs(configs)
|
370
|
-
headings = [
|
618
|
+
headings = ["ID", "Wifi Name", "Config Vers."]
|
371
619
|
rows = configs.map do |hash|
|
372
620
|
[hash[:id], hash[:name], hash[:version]]
|
373
621
|
end
|
@@ -376,25 +624,24 @@ module EnfCli
|
|
376
624
|
end
|
377
625
|
|
378
626
|
# Display a single wifi configuration in detail
|
379
|
-
def display_wifi_detail(wifi_data, full_listing=true)
|
627
|
+
def display_wifi_detail(wifi_data, full_listing = true, tabs = 0)
|
628
|
+
indent = " " * tabs
|
380
629
|
name = wifi_data[:name]
|
381
630
|
wifi_id = wifi_data[:id]
|
382
631
|
desc = wifi_data[:description]
|
383
632
|
nets = wifi_data[:networks]
|
384
633
|
|
385
|
-
say "
|
386
|
-
say "
|
387
|
-
say "Description : #{desc}", nil, true if desc
|
634
|
+
say indent + "Name : #{name}", nil, true
|
635
|
+
say indent + "Wifi ID : #{wifi_id}", nil, true
|
636
|
+
say indent + "Description : #{desc}", nil, true if desc
|
388
637
|
if full_listing
|
389
638
|
say "WiFi Networks :"
|
390
639
|
if nets
|
391
640
|
nets.each do |wifi_net|
|
392
|
-
display_wifi_net(wifi_net, 1)
|
641
|
+
display_wifi_net(wifi_net, tabs + 1)
|
393
642
|
end
|
394
643
|
end
|
395
|
-
|
396
644
|
end
|
397
|
-
|
398
645
|
end
|
399
646
|
|
400
647
|
def display_wifi_net(wifi_net, tabs)
|
@@ -412,7 +659,7 @@ module EnfCli
|
|
412
659
|
display_ipv6_addr(wifi_net[:IPv6], tabs + 1)
|
413
660
|
end
|
414
661
|
|
415
|
-
def display_ipv4_addr
|
662
|
+
def display_ipv4_addr(ipv4, tabs)
|
416
663
|
indent = " " * tabs
|
417
664
|
if ipv4.instance_of? String
|
418
665
|
say indent + "IPv4 : #{ipv4}", nil, true
|
@@ -435,7 +682,7 @@ module EnfCli
|
|
435
682
|
end
|
436
683
|
end
|
437
684
|
|
438
|
-
def display_ipv6_addr
|
685
|
+
def display_ipv6_addr(ipv6, tabs)
|
439
686
|
indent = " " * tabs
|
440
687
|
if ipv6.instance_of? String
|
441
688
|
say indent + "IPv6 : #{ipv6}", nil, true
|
@@ -458,8 +705,6 @@ module EnfCli
|
|
458
705
|
end
|
459
706
|
end
|
460
707
|
|
461
|
-
|
462
|
-
|
463
708
|
#
|
464
709
|
# display the device info.
|
465
710
|
# device_data is a hash matching the json structure
|
@@ -469,8 +714,8 @@ module EnfCli
|
|
469
714
|
ctl_addr = device_data[:control_address]
|
470
715
|
dev_addr = device_data[:device_address] || "\n"
|
471
716
|
mac_addrs = device_data[:mac_address]
|
472
|
-
firmware = device_data[:firmware_version] ||
|
473
|
-
model = device_data[:model] ||
|
717
|
+
firmware = device_data[:firmware_version] || "< not available >"
|
718
|
+
model = device_data[:model] || "< not available >"
|
474
719
|
profile = device_data[:profile]
|
475
720
|
status = device_data[:status]
|
476
721
|
|
@@ -485,18 +730,18 @@ module EnfCli
|
|
485
730
|
mac4 = mac_addrs[:'4']
|
486
731
|
|
487
732
|
if !mac1
|
488
|
-
say
|
733
|
+
say "Mac Address : < not available >"
|
489
734
|
elsif !mac2 && !mac3 && !mac4
|
490
735
|
say "Mac Address : #{mac1}", nil, true
|
491
736
|
else
|
492
|
-
say
|
737
|
+
say "Mac Address :"
|
493
738
|
say " 1 : #{mac1}", nil, true
|
494
739
|
say " 2 : #{mac2}", nil, true if mac2
|
495
740
|
say " 3 : #{mac3}", nil, true if mac3
|
496
741
|
say " 4 : #{mac4}", nil, true if mac4
|
497
742
|
end
|
498
743
|
else
|
499
|
-
say
|
744
|
+
say "Mac Address : < not available >"
|
500
745
|
end
|
501
746
|
|
502
747
|
say "Firmware Version : #{firmware}", nil, true
|
@@ -508,19 +753,19 @@ module EnfCli
|
|
508
753
|
say "Profile : < not available >"
|
509
754
|
end
|
510
755
|
display_device_status_summary status
|
511
|
-
say
|
756
|
+
say " ", nil, true
|
512
757
|
end
|
513
758
|
|
514
|
-
#
|
759
|
+
#
|
515
760
|
# display the status summary
|
516
761
|
#
|
517
|
-
def display_device_status_summary
|
762
|
+
def display_device_status_summary(device_status)
|
518
763
|
if device_status
|
519
|
-
mode = device_status[:router_mode] || device_status[:mode] ||
|
764
|
+
mode = device_status[:router_mode] || device_status[:mode] || "< not available >"
|
520
765
|
|
521
766
|
wifi = device_status[:wifi]
|
522
767
|
|
523
|
-
say
|
768
|
+
say "Status :"
|
524
769
|
say " Router Mode : #{mode}", nil, true
|
525
770
|
|
526
771
|
if wifi
|
@@ -539,21 +784,21 @@ module EnfCli
|
|
539
784
|
# status is a hash matching the json structure
|
540
785
|
#
|
541
786
|
def display_device_status(device_status)
|
542
|
-
sn = device_status[:serial_number] ||
|
543
|
-
mode = device_status[:router_mode] || device_status[:mode] ||
|
787
|
+
sn = device_status[:serial_number] || "< not available >"
|
788
|
+
mode = device_status[:router_mode] || device_status[:mode] || "< not available >"
|
544
789
|
|
545
|
-
uptime = device_status[:uptime] ||
|
546
|
-
refresh = device_status[:refresh_time] ||
|
790
|
+
uptime = device_status[:uptime] || "< not available >"
|
791
|
+
refresh = device_status[:refresh_time] || "< not available >"
|
547
792
|
|
548
793
|
wifi = device_status[:wifi]
|
549
794
|
if wifi
|
550
|
-
connected = wifi[:connected] ||
|
795
|
+
connected = wifi[:connected] || "< not available >"
|
551
796
|
ssid = wifi[:SSID]
|
552
797
|
ipv4 = wifi[:IPv4_addresses]
|
553
798
|
ipv6 = wifi[:IPv6_addresses]
|
554
799
|
wifi_config = wifi[:config]
|
555
800
|
else
|
556
|
-
connected =
|
801
|
+
connected = "< not available >"
|
557
802
|
ssid = nil
|
558
803
|
ipv4 = nil
|
559
804
|
ipv6 = nil
|
@@ -564,7 +809,7 @@ module EnfCli
|
|
564
809
|
say "Router Mode : #{mode}", nil, true
|
565
810
|
say "Uptime (in seconds) : #{uptime}", nil, true
|
566
811
|
say "Status refresh time : #{refresh}", nil, true
|
567
|
-
say "WIFI
|
812
|
+
say "WIFI status :"
|
568
813
|
say " connected : #{connected}", nil, true
|
569
814
|
say " SSID : #{ssid}" if ssid
|
570
815
|
if ipv4 && !ipv4.empty?
|
@@ -581,17 +826,27 @@ module EnfCli
|
|
581
826
|
end
|
582
827
|
end
|
583
828
|
|
584
|
-
|
829
|
+
say "WIFI configuration :"
|
830
|
+
display_wifi_detail(wifi_config, false, 1) if wifi_config
|
831
|
+
|
832
|
+
fw_status = device_status[:firmware]
|
833
|
+
if fw_status
|
834
|
+
image_name = fw_status[:image_name] || "< not available >"
|
835
|
+
image_state = fw_status[:state] || " < not available >"
|
836
|
+
say "Firmware Status :"
|
837
|
+
say " image name : #{image_name}", nil, true
|
838
|
+
say " operating state : #{image_state}", nil, true
|
839
|
+
end
|
585
840
|
|
586
|
-
say
|
841
|
+
say " ", nil, true
|
587
842
|
end
|
588
843
|
|
589
844
|
#
|
590
845
|
# display the devices summary list
|
591
846
|
#
|
592
847
|
def display_device_list(devices)
|
593
|
-
headings = [
|
594
|
-
|
848
|
+
headings = ["Serial No", "Dev Name", "Dev Addr", "Router Mode",
|
849
|
+
"Connected", "SSID"]
|
595
850
|
rows = devices.map do |hash|
|
596
851
|
status = hash[:status]
|
597
852
|
if status
|
@@ -601,16 +856,15 @@ module EnfCli
|
|
601
856
|
mode = status[:router_mode] || status[:mode]
|
602
857
|
|
603
858
|
[hash[:serial_number], hash[:device_name], hash[:device_address],
|
604
|
-
|
859
|
+
mode, connected, ssid]
|
605
860
|
else
|
606
861
|
[hash[:serial_number], hash[:device_name], hash[:device_address],
|
607
|
-
|
862
|
+
nil, nil, nil]
|
608
863
|
end
|
609
864
|
end
|
610
865
|
# [hash[:serial_number], hash[:device_name], hash[:device_address],
|
611
866
|
# hash[:status][:router_mode], hash[:status][:wifi][:connected],
|
612
867
|
# hash[:status][:wifi][:SSID]]
|
613
|
-
|
614
868
|
render_table(headings, rows)
|
615
869
|
end
|
616
870
|
|
@@ -618,13 +872,22 @@ module EnfCli
|
|
618
872
|
# Display profile detail
|
619
873
|
#
|
620
874
|
def display_profile(profile, summary = false)
|
621
|
-
indent = summary ?
|
875
|
+
indent = summary ? " " : ""
|
876
|
+
|
877
|
+
firmware = profile[:config][:firmware]
|
878
|
+
fw_id = "< not configured >"
|
879
|
+
if firmware
|
880
|
+
fw_id = firmware[:id]
|
881
|
+
fw_version = firmware[:version]
|
882
|
+
end
|
622
883
|
|
623
884
|
say indent + "Name : #{profile[:name]}", nil, true
|
624
885
|
say indent + "Profile ID : #{profile[:id]}", nil, true
|
625
886
|
say indent + "Configuration version : #{profile[:config][:version]}", nil, true
|
887
|
+
say indent + "Firmware Update ID : #{fw_id}", nil, true
|
626
888
|
unless summary
|
627
|
-
say "
|
889
|
+
say indent + " Update version : #{fw_version}", nil, true if fw_version
|
890
|
+
say indent + "Mode : #{profile[:config][:mode]}", nil, true
|
628
891
|
display_wifi_summary profile[:config][:wifi]
|
629
892
|
end
|
630
893
|
end
|
@@ -632,7 +895,7 @@ module EnfCli
|
|
632
895
|
#
|
633
896
|
# Display summary of the WIFI configuration info
|
634
897
|
#
|
635
|
-
def display_wifi_summary
|
898
|
+
def display_wifi_summary(wifi)
|
636
899
|
if wifi
|
637
900
|
say "Wifi config :"
|
638
901
|
say " id : #{wifi[:id]}", nil, true
|
@@ -645,22 +908,120 @@ module EnfCli
|
|
645
908
|
else
|
646
909
|
say "Wifi config : < not configured >"
|
647
910
|
end
|
648
|
-
|
649
911
|
end
|
650
912
|
|
651
913
|
#
|
652
914
|
# display the profile summary list
|
653
915
|
#
|
654
916
|
def display_profile_list(profiles)
|
655
|
-
headings = [
|
917
|
+
headings = ["Profile Name", "Profile ID", "Version", "Mode", "Wifi ID", "Firmware Update ID"]
|
656
918
|
rows = profiles.map do |hash|
|
657
|
-
|
919
|
+
config = hash[:config]
|
920
|
+
fw_id = config[:firmware] ? config[:firmware][:id] : "none"
|
921
|
+
[hash[:name], hash[:id], config[:version], config[:mode],
|
922
|
+
config[:wifi][:id], fw_id]
|
658
923
|
end
|
924
|
+
render_table(headings, rows)
|
925
|
+
end
|
659
926
|
|
927
|
+
#
|
928
|
+
# Displays the list of firmware releases
|
929
|
+
#
|
930
|
+
def display_firmware_image_list(images)
|
931
|
+
headings = ["Firmware Version"]
|
932
|
+
rows = images.map do |element|
|
933
|
+
[element[:version]]
|
934
|
+
end
|
935
|
+
render_table(headings, rows)
|
936
|
+
end
|
937
|
+
|
938
|
+
#
|
939
|
+
# Displays firmware detail
|
940
|
+
#
|
941
|
+
def display_firmware_detail(fw_info)
|
942
|
+
version = fw_info[:version] || "< not available >"
|
943
|
+
images = fw_info[:images]
|
944
|
+
|
945
|
+
say "Release version: #{version}"
|
946
|
+
display_image_list images
|
947
|
+
end
|
948
|
+
|
949
|
+
#
|
950
|
+
# Displays the list of images available for a release version.
|
951
|
+
#
|
952
|
+
def display_image_list(images)
|
953
|
+
headings = ["Image Name", "Hardware Model", "Update Type", "SHA256"]
|
954
|
+
rows = images.map do |hash|
|
955
|
+
[hash[:name], hash[:model], hash[:type], hash[:sha256]]
|
956
|
+
end
|
957
|
+
render_table(headings, rows)
|
958
|
+
end
|
959
|
+
|
960
|
+
#
|
961
|
+
# Displays the full SCHEDULE object detail
|
962
|
+
#
|
963
|
+
def display_schedule_detail(sched_data)
|
964
|
+
name = sched_data[:name]
|
965
|
+
id = sched_data[:id]
|
966
|
+
domain = sched_data[:domain]
|
967
|
+
times = sched_data[:times]
|
968
|
+
|
969
|
+
say "Schedule Name : #{name}", nil, true
|
970
|
+
say "Shedule ID : #{id}", nil, true
|
971
|
+
say "domain : #{domain}", nil, true
|
972
|
+
say "Times:", nil, true
|
973
|
+
display_time_list(times)
|
974
|
+
end
|
975
|
+
|
976
|
+
#
|
977
|
+
# Displays the list of times in a schedule as a table
|
978
|
+
#
|
979
|
+
def display_time_list(times)
|
980
|
+
headings = ["Name", "year", "Month", "Date", "Weekday", "Hour",
|
981
|
+
"Minute", "id"]
|
982
|
+
rows = times.map do |hash|
|
983
|
+
[hash[:name], hash[:year], hash[:month], hash[:day_of_month],
|
984
|
+
hash[:day_of_week], hash[:hour], hash[:minute], hash[:id]]
|
985
|
+
end
|
986
|
+
render_table(headings, rows)
|
987
|
+
end
|
988
|
+
|
989
|
+
#
|
990
|
+
# Displays a list of schedules
|
991
|
+
#
|
992
|
+
def display_schedule_list(sched_list)
|
993
|
+
headings = ["Name", "ID"]
|
994
|
+
rows = sched_list.map do |hash|
|
995
|
+
[hash[:name], hash[:id]]
|
996
|
+
end
|
660
997
|
render_table(headings, rows)
|
661
998
|
end
|
662
999
|
|
1000
|
+
#
|
1001
|
+
# Displays a list of firmware updates
|
1002
|
+
#
|
1003
|
+
def display_updates_list(updates_list)
|
1004
|
+
headings = ["Update ID", "Update Version"]
|
1005
|
+
rows = updates_list.map do |hash|
|
1006
|
+
[hash[:id], hash[:version]]
|
1007
|
+
end
|
1008
|
+
render_table(headings, rows)
|
1009
|
+
end
|
663
1010
|
|
1011
|
+
#
|
1012
|
+
# Displays detail of a specific firmware update task
|
1013
|
+
#
|
1014
|
+
def display_update_detail(update_data)
|
1015
|
+
id = update_data[:id]
|
1016
|
+
sched_id = update_data[:schedule_id]
|
1017
|
+
version = update_data[:version]
|
1018
|
+
percent = update_data[:update_percentage]
|
1019
|
+
|
1020
|
+
say "Update Task ID : #{id}"
|
1021
|
+
say "Schedule ID : #{sched_id}"
|
1022
|
+
say "Firmware Version : #{version}"
|
1023
|
+
say "Percent of devices to update : #{percent}"
|
1024
|
+
end
|
664
1025
|
end
|
665
1026
|
end
|
666
1027
|
end
|