MuranoCLI 3.0.0 → 3.0.1
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/.rubocop.yml +50 -27
- data/.trustme.vim +12 -8
- data/bin/murano +23 -8
- data/docs/basic_example.rst +1 -1
- data/docs/completions/murano_completion-bash +88 -0
- data/lib/MrMurano/Account.rb +3 -3
- data/lib/MrMurano/Business.rb +6 -6
- data/lib/MrMurano/Config-Migrate.rb +1 -3
- data/lib/MrMurano/Config.rb +16 -8
- data/lib/MrMurano/Content.rb +56 -45
- data/lib/MrMurano/Gateway.rb +62 -21
- data/lib/MrMurano/Mock.rb +27 -19
- data/lib/MrMurano/Passwords.rb +7 -7
- data/lib/MrMurano/ReCommander.rb +171 -28
- data/lib/MrMurano/Setting.rb +38 -40
- data/lib/MrMurano/Solution-ServiceConfig.rb +2 -1
- data/lib/MrMurano/Solution-Services.rb +196 -61
- data/lib/MrMurano/Solution-Users.rb +7 -7
- data/lib/MrMurano/Solution.rb +22 -8
- data/lib/MrMurano/SolutionId.rb +10 -4
- data/lib/MrMurano/SubCmdGroupContext.rb +14 -5
- data/lib/MrMurano/SyncAllowed.rb +42 -0
- data/lib/MrMurano/SyncUpDown.rb +122 -65
- data/lib/MrMurano/Webservice-Cors.rb +9 -3
- data/lib/MrMurano/Webservice-Endpoint.rb +39 -33
- data/lib/MrMurano/Webservice-File.rb +35 -24
- data/lib/MrMurano/commands/business.rb +18 -18
- data/lib/MrMurano/commands/content.rb +3 -2
- data/lib/MrMurano/commands/devices.rb +137 -22
- data/lib/MrMurano/commands/globals.rb +8 -2
- data/lib/MrMurano/commands/keystore.rb +3 -2
- data/lib/MrMurano/commands/link.rb +13 -13
- data/lib/MrMurano/commands/login.rb +3 -2
- data/lib/MrMurano/commands/mock.rb +4 -3
- data/lib/MrMurano/commands/password.rb +4 -2
- data/lib/MrMurano/commands/postgresql.rb +5 -3
- data/lib/MrMurano/commands/settings.rb +78 -62
- data/lib/MrMurano/commands/show.rb +79 -74
- data/lib/MrMurano/commands/solution.rb +6 -4
- data/lib/MrMurano/commands/solution_picker.rb +5 -4
- data/lib/MrMurano/commands/status.rb +15 -4
- data/lib/MrMurano/commands/timeseries.rb +3 -2
- data/lib/MrMurano/commands/tsdb.rb +3 -2
- data/lib/MrMurano/hash.rb +6 -6
- data/lib/MrMurano/http.rb +66 -67
- data/lib/MrMurano/makePretty.rb +18 -12
- data/lib/MrMurano/progress.rb +9 -2
- data/lib/MrMurano/verbosing.rb +14 -2
- data/lib/MrMurano/version.rb +2 -2
- data/spec/GatewayDevice_spec.rb +190 -149
- data/spec/Mock_spec.rb +3 -3
- data/spec/Solution-ServiceEventHandler_spec.rb +170 -137
- data/spec/SyncUpDown_spec.rb +205 -191
- metadata +3 -2
@@ -1,4 +1,4 @@
|
|
1
|
-
# Last Modified: 2017.08.
|
1
|
+
# Last Modified: 2017.08.22 /coding: utf-8
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
# Copyright © 2016-2017 Exosite LLC.
|
@@ -59,10 +59,16 @@ module MrMurano
|
|
59
59
|
if !file.nil?
|
60
60
|
data = YAML.load_file(file)
|
61
61
|
else
|
62
|
-
|
62
|
+
file = $project['routes.cors']
|
63
63
|
# If it is just a string, then is a file to load.
|
64
|
-
|
64
|
+
if file.is_a? String
|
65
|
+
data = YAML.load_file(file)
|
66
|
+
else
|
67
|
+
data = file
|
68
|
+
file = %(ProjectFile['routes.cors'])
|
69
|
+
end
|
65
70
|
end
|
71
|
+
return unless upload_item_allowed(file)
|
66
72
|
put('', data)
|
67
73
|
end
|
68
74
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Last Modified: 2017.08.
|
1
|
+
# Last Modified: 2017.08.22 /coding: utf-8
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
# Copyright © 2016-2017 Exosite LLC.
|
@@ -16,7 +16,6 @@ module MrMurano
|
|
16
16
|
# …/endpoint
|
17
17
|
module Webservice
|
18
18
|
class Endpoint < WebserviceBase
|
19
|
-
|
20
19
|
# Route Specific details on an Item
|
21
20
|
class RouteItem < Item
|
22
21
|
# @return [String] HTTP method for this endpoint
|
@@ -49,7 +48,7 @@ module MrMurano
|
|
49
48
|
ret = get
|
50
49
|
return [] unless ret.is_a?(Array)
|
51
50
|
ret.map do |item|
|
52
|
-
if item[:content_type].to_s.empty?
|
51
|
+
if item[:content_type].to_s.empty?
|
53
52
|
item[:content_type] = 'application/json'
|
54
53
|
end
|
55
54
|
# XXX should this update the script header?
|
@@ -69,9 +68,9 @@ module MrMurano
|
|
69
68
|
|
70
69
|
ret[:content_type] = 'application/json' if ret[:content_type].empty?
|
71
70
|
|
72
|
-
script = ret[:script].lines.map
|
71
|
+
script = ret[:script].lines.map(&:chomp)
|
73
72
|
|
74
|
-
aheader = (script.first
|
73
|
+
aheader = (script.first || '')
|
75
74
|
|
76
75
|
rh = ['--#ENDPOINT', ret[:method].upcase, ret[:path]]
|
77
76
|
rh << ret[:content_type] if ret[:content_type] != 'application/json'
|
@@ -81,19 +80,21 @@ module MrMurano
|
|
81
80
|
# If header is wrong, replace it.
|
82
81
|
|
83
82
|
md = @match_header.match(aheader)
|
84
|
-
if md.nil?
|
83
|
+
if md.nil?
|
85
84
|
# header missing.
|
86
85
|
script.unshift rheader
|
87
|
-
elsif
|
88
|
-
md[:
|
89
|
-
md[:
|
86
|
+
elsif (
|
87
|
+
md[:method] != ret[:method] ||
|
88
|
+
md[:path] != ret[:path] ||
|
89
|
+
md[:ctype] != ret[:content_type]
|
90
|
+
)
|
90
91
|
# header is wrong.
|
91
92
|
script[0] = rheader
|
92
93
|
end
|
93
94
|
# otherwise current header is good.
|
94
95
|
|
95
96
|
script = script.join("\n") + "\n"
|
96
|
-
if block_given?
|
97
|
+
if block_given?
|
97
98
|
yield script
|
98
99
|
else
|
99
100
|
script
|
@@ -105,23 +106,24 @@ module MrMurano
|
|
105
106
|
# @param local [Pathname] path to file to push
|
106
107
|
# @param remote [RouteItem] of method and endpoint path
|
107
108
|
# @param modify [Boolean] True if item exists already and this is changing it
|
108
|
-
def upload(local, remote,
|
109
|
-
local = Pathname.new(local) unless local.
|
110
|
-
raise
|
109
|
+
def upload(local, remote, _modify)
|
110
|
+
local = Pathname.new(local) unless local.is_a? Pathname
|
111
|
+
raise 'no file' unless local.exist?
|
111
112
|
# we assume these are small enough to slurp.
|
112
|
-
if remote.script.nil?
|
113
|
+
if remote.script.nil?
|
113
114
|
script = local.read
|
114
115
|
remote[:script] = script
|
115
116
|
end
|
116
117
|
limitkeys = [:method, :path, :script, :content_type, @itemkey]
|
117
|
-
remote = remote.to_h.select{|k,
|
118
|
-
|
119
|
-
|
118
|
+
remote = remote.to_h.select { |k, _v| limitkeys.include? k }
|
119
|
+
if remote.key? @itemkey
|
120
|
+
return unless upload_item_allowed(remote[@itemkey])
|
120
121
|
put('/' + remote[@itemkey], remote) do |request, http|
|
121
122
|
response = http.request(request)
|
122
123
|
case response
|
123
124
|
when Net::HTTPSuccess
|
124
125
|
#return JSON.parse(response.body)
|
126
|
+
return
|
125
127
|
when Net::HTTPNotFound
|
126
128
|
verbose "\tDoesn't exist, creating"
|
127
129
|
post('/', remote)
|
@@ -131,6 +133,8 @@ module MrMurano
|
|
131
133
|
end
|
132
134
|
else
|
133
135
|
verbose "\tNo itemkey, creating"
|
136
|
+
#return unless upload_item_allowed(remote)
|
137
|
+
return unless upload_item_allowed(local)
|
134
138
|
post('', remote)
|
135
139
|
end
|
136
140
|
end
|
@@ -138,29 +142,31 @@ module MrMurano
|
|
138
142
|
##
|
139
143
|
# Delete an endpoint
|
140
144
|
def remove(id)
|
145
|
+
return unless remove_item_allowed(id)
|
141
146
|
delete('/' + id.to_s)
|
142
147
|
end
|
143
148
|
|
144
|
-
def tolocalname(item,
|
149
|
+
def tolocalname(item, _key)
|
145
150
|
name = ''
|
146
151
|
# 2017-07-02: Changing shovel operator << to +=
|
147
152
|
# to support Ruby 3.0 frozen string literals.
|
148
|
-
name += item[:path].split('/').reject
|
153
|
+
name += item[:path].split('/').reject(&:empty?).join('-')
|
149
154
|
name += '.'
|
150
155
|
# This downcase is just for the filename.
|
151
156
|
name += item[:method].downcase
|
152
157
|
name += '.lua'
|
158
|
+
name
|
153
159
|
end
|
154
160
|
|
155
|
-
def to_remote_item(
|
161
|
+
def to_remote_item(_from, path)
|
156
162
|
# Path could be have multiple endpoints in side, so a loop.
|
157
163
|
items = []
|
158
|
-
path = Pathname.new(path) unless path.
|
164
|
+
path = Pathname.new(path) unless path.is_a? Pathname
|
159
165
|
cur = nil
|
160
166
|
lineno = 0
|
161
|
-
path.readlines
|
167
|
+
path.readlines.each do |line|
|
162
168
|
md = @match_header.match(line)
|
163
|
-
if
|
169
|
+
if !md.nil?
|
164
170
|
# header line.
|
165
171
|
cur[:line_end] = lineno unless cur.nil?
|
166
172
|
items << cur unless cur.nil?
|
@@ -181,12 +187,12 @@ module MrMurano
|
|
181
187
|
#method: md[:method],
|
182
188
|
method: md[:method].upcase,
|
183
189
|
path: md[:path],
|
184
|
-
content_type: (md[:ctype]
|
190
|
+
content_type: (md[:ctype] || 'application/json'),
|
185
191
|
local_path: path,
|
186
192
|
line: lineno,
|
187
193
|
script: up_line,
|
188
194
|
)
|
189
|
-
elsif
|
195
|
+
elsif !cur.nil? && !cur[:script].nil?
|
190
196
|
# 2017-07-02: Frozen string literal: change << to +=
|
191
197
|
cur[:script] += line
|
192
198
|
end
|
@@ -204,27 +210,27 @@ module MrMurano
|
|
204
210
|
return false if md.nil?
|
205
211
|
debug "match pattern: '#{md[:method]}' '#{md[:path]}'"
|
206
212
|
|
207
|
-
unless md[:method].empty?
|
213
|
+
unless md[:method].empty?
|
208
214
|
return false unless item[:method].casecmp(md[:method]).zero?
|
209
215
|
end
|
210
216
|
|
211
217
|
return true if md[:path].empty?
|
212
218
|
|
213
|
-
::File.fnmatch(md[:path],item[:path])
|
219
|
+
::File.fnmatch(md[:path], item[:path])
|
214
220
|
end
|
215
221
|
|
216
222
|
def synckey(item)
|
217
223
|
"#{item[:method].upcase}_#{item[:path]}"
|
218
224
|
end
|
219
225
|
|
220
|
-
def docmp(
|
221
|
-
if
|
222
|
-
|
226
|
+
def docmp(item_a, item_b)
|
227
|
+
if item_a[:script].nil? && item_a[:local_path]
|
228
|
+
item_a[:script] = item_a[:local_path].read
|
223
229
|
end
|
224
|
-
if
|
225
|
-
|
230
|
+
if item_b[:script].nil? && item_b[:local_path]
|
231
|
+
item_b[:script] = item_b[:local_path].read
|
226
232
|
end
|
227
|
-
|
233
|
+
(item_a[:script] != item_b[:script] || item_a[:content_type] != item_b[:content_type])
|
228
234
|
end
|
229
235
|
end
|
230
236
|
|
@@ -1,5 +1,12 @@
|
|
1
|
+
# Last Modified: 2017.08.22 /coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Copyright © 2016-2017 Exosite LLC.
|
5
|
+
# License: MIT. See LICENSE.txt.
|
6
|
+
# vim:tw=0:ts=2:sw=2:et:ai
|
7
|
+
|
1
8
|
require 'digest/sha1'
|
2
|
-
require
|
9
|
+
require 'http/form_data'
|
3
10
|
require 'mime/types'
|
4
11
|
require 'net/http'
|
5
12
|
require 'uri'
|
@@ -50,12 +57,12 @@ module MrMurano
|
|
50
57
|
# Get one item of the static content.
|
51
58
|
def fetch(path, &block)
|
52
59
|
path = path[1..-1] if path[0] == '/'
|
53
|
-
path = '/'+ URI.encode_www_form_component(path)
|
60
|
+
path = '/' + URI.encode_www_form_component(path)
|
54
61
|
get(path) do |request, http|
|
55
62
|
http.request(request) do |resp|
|
56
63
|
case resp
|
57
64
|
when Net::HTTPSuccess
|
58
|
-
if block_given?
|
65
|
+
if block_given?
|
59
66
|
resp.read_body(&block)
|
60
67
|
else
|
61
68
|
resp.read_body do |chunk|
|
@@ -75,15 +82,14 @@ module MrMurano
|
|
75
82
|
# @param path [String] The identifying key for this item
|
76
83
|
def remove(path)
|
77
84
|
path = path[1..-1] if path[0] == '/'
|
85
|
+
return unless remove_item_allowed(path)
|
78
86
|
delete('/' + URI.encode_www_form_component(path))
|
79
87
|
end
|
80
88
|
|
81
89
|
def curldebug(request)
|
82
90
|
# The upload will get printed out inside of upload.
|
83
91
|
# Because we don't have the correct info here.
|
84
|
-
if request.method != 'PUT'
|
85
|
-
super(request)
|
86
|
-
end
|
92
|
+
super(request) if request.method != 'PUT'
|
87
93
|
end
|
88
94
|
|
89
95
|
##
|
@@ -91,8 +97,8 @@ module MrMurano
|
|
91
97
|
# @param src [Pathname] Full path of where to upload from
|
92
98
|
# @param item [Hash] The item details to upload
|
93
99
|
# @param modify [Boolean] True if item exists already and this is changing it
|
94
|
-
def upload(local, remote,
|
95
|
-
local = Pathname.new(local) unless local.
|
100
|
+
def upload(local, remote, _modify)
|
101
|
+
local = Pathname.new(local) unless local.is_a? Pathname
|
96
102
|
|
97
103
|
path = remote[:path]
|
98
104
|
path = path[1..-1] if path[0] == '/'
|
@@ -110,22 +116,25 @@ module MrMurano
|
|
110
116
|
# Most of these pull into ram. So maybe just go with that. Would guess that
|
111
117
|
# truely large static content is rare, and we can optimize/fix that later.
|
112
118
|
|
113
|
-
file = HTTP::FormData::File.new(local.to_s,
|
119
|
+
file = HTTP::FormData::File.new(local.to_s, content_type: remote[:mime_type])
|
114
120
|
form = HTTP::FormData.create(file: file)
|
115
121
|
req = Net::HTTP::Put.new(uri)
|
116
|
-
|
122
|
+
add_headers(req)
|
123
|
+
|
124
|
+
return unless upload_item_allowed(remote[@itemkey])
|
125
|
+
|
117
126
|
workit(req) do |request, http|
|
118
127
|
request.content_type = form.content_type
|
119
128
|
request.content_length = form.content_length
|
120
129
|
request.body = form.to_s
|
121
130
|
|
122
|
-
if $cfg['tool.curldebug']
|
131
|
+
if $cfg['tool.curldebug']
|
123
132
|
a = []
|
124
|
-
a << %
|
125
|
-
a << %
|
126
|
-
a << %
|
127
|
-
a << %
|
128
|
-
a << %
|
133
|
+
a << %(curl -s -H 'Authorization: #{request['authorization']}')
|
134
|
+
a << %(-H 'User-Agent: #{request['User-Agent']}')
|
135
|
+
a << %(-X #{request.method})
|
136
|
+
a << %('#{request.uri}')
|
137
|
+
a << %(-F file=@#{local})
|
129
138
|
if $cfg.curlfile_f.nil?
|
130
139
|
puts a.join(' ')
|
131
140
|
else
|
@@ -156,20 +165,22 @@ module MrMurano
|
|
156
165
|
name = '/' if name == $cfg['files.default_page']
|
157
166
|
name = "/#{name}" unless name.chars.first == '/'
|
158
167
|
|
159
|
-
mime = MIME::Types.type_for(path.to_s)[0] || MIME::Types[
|
168
|
+
mime = MIME::Types.type_for(path.to_s)[0] || MIME::Types['application/octet-stream'][0]
|
160
169
|
|
161
170
|
# It does not actually take the SHA1 of the file.
|
162
171
|
# It first converts the file to hex, then takes the SHA1 of that string
|
163
172
|
#sha1 = Digest::SHA1.file(path.to_s).hexdigest
|
164
173
|
sha1 = Digest::SHA1.new
|
165
174
|
path.open('rb:ASCII-8BIT') do |io|
|
166
|
-
|
175
|
+
# rubocop:disable Lint/AssignmentInCondition
|
176
|
+
# "Assignment in condition - you probably meant to use ==."
|
177
|
+
while chunk = io.read(1_048_576)
|
167
178
|
sha1 << Digest.hexencode(chunk)
|
168
179
|
end
|
169
180
|
end
|
170
181
|
debug "Checking #{name} (#{mime.simplified} #{sha1.hexdigest})"
|
171
182
|
|
172
|
-
FileItem.new(:
|
183
|
+
FileItem.new(path: name, mime_type: mime.simplified, checksum: sha1.hexdigest)
|
173
184
|
end
|
174
185
|
|
175
186
|
# @param item [FileItem] The item to get a key from
|
@@ -179,11 +190,11 @@ module MrMurano
|
|
179
190
|
end
|
180
191
|
|
181
192
|
# Compare items.
|
182
|
-
# @param
|
183
|
-
# @param
|
184
|
-
def docmp(
|
185
|
-
|
186
|
-
|
193
|
+
# @param item_a [FileItem]
|
194
|
+
# @param item_b [FileItem]
|
195
|
+
def docmp(item_a, item_b)
|
196
|
+
(item_a[:mime_type] != item_b[:mime_type] ||
|
197
|
+
item_a[:checksum] != item_b[:checksum])
|
187
198
|
end
|
188
199
|
end
|
189
200
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Last Modified: 2017.08.
|
1
|
+
# Last Modified: 2017.08.23 /coding: utf-8
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
# Copyright © 2016-2017 Exosite LLC.
|
@@ -10,7 +10,7 @@ require 'MrMurano/Account'
|
|
10
10
|
require 'MrMurano/Business'
|
11
11
|
require 'MrMurano/ReCommander'
|
12
12
|
|
13
|
-
MSG_BUSINESSES_NONE_FOUND = 'No businesses found'
|
13
|
+
MSG_BUSINESSES_NONE_FOUND = 'No businesses found' unless defined? MSG_BUSINESSES_NONE_FOUND
|
14
14
|
|
15
15
|
# *** Base business command help.
|
16
16
|
# -------------------------------
|
@@ -22,9 +22,10 @@ command :business do |c|
|
|
22
22
|
Commands for working with businesses.
|
23
23
|
).strip
|
24
24
|
c.project_not_required = true
|
25
|
+
c.subcmdgrouphelp = true
|
25
26
|
|
26
27
|
c.action do |_args, _options|
|
27
|
-
::Commander::UI.enable_paging
|
28
|
+
::Commander::UI.enable_paging unless $cfg['tool.no-page']
|
28
29
|
say MrMurano::SubCmdGroupHelp.new(c).get_help
|
29
30
|
end
|
30
31
|
end
|
@@ -49,19 +50,17 @@ def cmd_options_add_id_and_name(c)
|
|
49
50
|
end
|
50
51
|
|
51
52
|
def cmd_defaults_id_and_name(options)
|
52
|
-
if
|
53
|
-
|
54
|
-
|
55
|
-
end
|
53
|
+
return if options.id.nil? || options.name.nil?
|
54
|
+
MrMurano::Verbose.error('Please specify only --id or --name but not both')
|
55
|
+
exit 1
|
56
56
|
end
|
57
57
|
|
58
58
|
def cmd_verify_args_and_id_or_name!(args, options)
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
59
|
+
return unless args.none? && (options.id || options.name)
|
60
|
+
MrMurano::Verbose.warning(
|
61
|
+
'The --id and --name options only apply when specifying a business name or ID.'
|
62
|
+
)
|
63
|
+
exit 1
|
65
64
|
end
|
66
65
|
|
67
66
|
def cmd_option_business_pickers(c)
|
@@ -123,7 +122,7 @@ Find business by name or ID.
|
|
123
122
|
c.action do |args, options|
|
124
123
|
# SKIP: c.verify_arg_count!(args)
|
125
124
|
cmd_defaults_id_and_name(options)
|
126
|
-
if
|
125
|
+
if args.none? && !any_business_pickers?(options)
|
127
126
|
MrMurano::Verbose.error('What would you like to find?')
|
128
127
|
exit 1
|
129
128
|
end
|
@@ -149,9 +148,9 @@ def business_find_or_ask!(acc, options)
|
|
149
148
|
biz = businesses_ask_which(acc) if biz.nil?
|
150
149
|
end
|
151
150
|
|
152
|
-
|
153
|
-
|
154
|
-
|
151
|
+
$cfg.set('business.id', nil, :internal)
|
152
|
+
$cfg.set('business.name', nil, :internal)
|
153
|
+
$cfg.set('business.fuzzy', nil, :internal)
|
155
154
|
|
156
155
|
biz
|
157
156
|
end
|
@@ -186,7 +185,8 @@ def business_from_config
|
|
186
185
|
if biz.valid?
|
187
186
|
say("Found Business #{biz.pretty_name_and_id}")
|
188
187
|
else
|
189
|
-
|
188
|
+
biz_bid = MrMurano::Verbose.fancy_ticks(biz.bid)
|
189
|
+
say("Could not find Business #{biz_bid} referenced in the config")
|
190
190
|
end
|
191
191
|
puts('')
|
192
192
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Last Modified: 2017.08.
|
1
|
+
# Last Modified: 2017.08.22 /coding: utf-8
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
# Copyright © 2016-2017 Exosite LLC.
|
@@ -17,9 +17,10 @@ This set of commands let you interact with the content area for a product.
|
|
17
17
|
This is where OTA data can be stored so that devices can easily download it.
|
18
18
|
).strip
|
19
19
|
c.project_not_required = true
|
20
|
+
c.subcmdgrouphelp = true
|
20
21
|
|
21
22
|
c.action do |_args, _options|
|
22
|
-
::Commander::UI.enable_paging
|
23
|
+
::Commander::UI.enable_paging unless $cfg['tool.no-page']
|
23
24
|
say MrMurano::SubCmdGroupHelp.new(c).get_help
|
24
25
|
end
|
25
26
|
end
|
@@ -1,23 +1,25 @@
|
|
1
|
-
# Last Modified: 2017.08.
|
1
|
+
# Last Modified: 2017.08.23 /coding: utf-8
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
# Copyright © 2016-2017 Exosite LLC.
|
5
5
|
# License: MIT. See LICENSE.txt.
|
6
6
|
# vim:tw=0:ts=2:sw=2:et:ai
|
7
7
|
|
8
|
+
require 'date'
|
8
9
|
require 'MrMurano/Gateway'
|
9
10
|
require 'MrMurano/ReCommander'
|
10
11
|
|
11
|
-
command
|
12
|
+
command :device do |c|
|
12
13
|
c.syntax = %(murano device)
|
13
14
|
c.summary = %(Interact with a device)
|
14
15
|
c.description = %(
|
15
16
|
Interact with a device.
|
16
17
|
).strip
|
17
18
|
c.project_not_required = true
|
19
|
+
c.subcmdgrouphelp = true
|
18
20
|
|
19
21
|
c.action do |_args, _options|
|
20
|
-
::Commander::UI.enable_paging
|
22
|
+
::Commander::UI.enable_paging unless $cfg['tool.no-page']
|
21
23
|
say MrMurano::SubCmdGroupHelp.new(c).get_help
|
22
24
|
end
|
23
25
|
end
|
@@ -90,7 +92,7 @@ end
|
|
90
92
|
alias_command 'devices list', 'device list'
|
91
93
|
|
92
94
|
command 'device read' do |c|
|
93
|
-
c.syntax = %(murano device read <identifier>
|
95
|
+
c.syntax = %(murano device read <identifier> [<alias>...] [--options])
|
94
96
|
c.summary = %(Read state of a device)
|
95
97
|
c.description = %(
|
96
98
|
Read state of a device.
|
@@ -101,7 +103,7 @@ This reads the latest state values for the resources in a device.
|
|
101
103
|
c.option '-o', '--output FILE', %(Download to file instead of STDOUT)
|
102
104
|
|
103
105
|
c.action do |args, options|
|
104
|
-
c.verify_arg_count!(args, nil, ['
|
106
|
+
c.verify_arg_count!(args, nil, ['Missing device identifier'])
|
105
107
|
|
106
108
|
prd = MrMurano::Gateway::Device.new
|
107
109
|
|
@@ -142,7 +144,7 @@ If an alias is not settable, this will fail.
|
|
142
144
|
).strip
|
143
145
|
|
144
146
|
c.action do |args, _options|
|
145
|
-
c.verify_arg_count!(args, nil, ['
|
147
|
+
c.verify_arg_count!(args, nil, ['Missing device identifier'])
|
146
148
|
|
147
149
|
resources = (MrMurano::Gateway::GweBase.new.info || {})[:resources]
|
148
150
|
|
@@ -174,45 +176,108 @@ If an alias is not settable, this will fail.
|
|
174
176
|
end
|
175
177
|
|
176
178
|
command 'device enable' do |c|
|
177
|
-
c.syntax = %(murano device enable
|
178
|
-
c.summary = %(Enable
|
179
|
+
c.syntax = %(murano device enable (<identifier>|--file <path>) [--options])
|
180
|
+
c.summary = %(Enable Identifiers in Murano for real world devices)
|
179
181
|
c.description = %(
|
180
|
-
Enables Identifiers, creating
|
182
|
+
Enables Identifiers, creating devices, or digital shadows, in Murano.
|
181
183
|
).strip
|
182
184
|
|
185
|
+
c.option '-e', '--expire HOURS', %(Devices that do not activate within HOURS hours will be deleted for security purposes)
|
183
186
|
c.option '-f', '--file FILE', %(A file of serial numbers, one per line)
|
184
|
-
c.option '--key FILE', %(
|
187
|
+
c.option '--key FILE', %(Path to file containing public TLS key for this device)
|
188
|
+
allowed_types = MrMurano::Gateway::Device::DEVICE_AUTH_TYPES.map(&:to_s).sort
|
189
|
+
c.option '--auth TYPE', %(Type of credential used to authenticate [#{allowed_types.join('|')}])
|
190
|
+
c.option '--cred KEY', %(The credential used to authenticate, e.g., token, password, etc.)
|
185
191
|
|
186
192
|
c.action do |args, options|
|
187
|
-
|
193
|
+
c.verify_arg_count!(args, 1)
|
194
|
+
|
188
195
|
prd = MrMurano::Gateway::Device.new
|
189
|
-
|
190
|
-
|
196
|
+
|
197
|
+
if args.count.zero? && options.file.to_s.empty?
|
198
|
+
prd.error 'Missing device identifier or --file'
|
191
199
|
exit 1
|
200
|
+
elsif !args.count.zero? && !options.file.to_s.empty?
|
201
|
+
prd.error 'Please specify an identifier or --file but not both'
|
202
|
+
exit 1
|
203
|
+
end
|
204
|
+
|
205
|
+
if !options.file.nil? && (!options.key.nil? || !options.auth.nil? || !options.cred.nil?)
|
206
|
+
prd.error %(Cannot use --file with any of: --key, --auth, or --cred)
|
207
|
+
exit 1
|
208
|
+
end
|
209
|
+
if !options.key.nil? && !options.cred.nil?
|
210
|
+
prd.error %(Please use either --cred or --key but not both)
|
211
|
+
exit 1
|
212
|
+
end
|
213
|
+
if options.auth.nil? ^ options.cred.nil?
|
214
|
+
prd.error %(Please specify both --auth and --cred or neither)
|
215
|
+
exit 1
|
216
|
+
end
|
217
|
+
options.auth = options.auth.to_sym unless options.auth.nil?
|
218
|
+
unless options.key.nil?
|
219
|
+
if !options.auth.nil? && options.auth != :certificate
|
220
|
+
prd.warning %(You probably mean to use "--auth certificate" with --key)
|
221
|
+
else
|
222
|
+
options.auth = :certificate
|
223
|
+
end
|
192
224
|
end
|
193
|
-
|
225
|
+
|
226
|
+
unless options.expire.nil?
|
227
|
+
unless options.expire =~ /^[0-9]+$/
|
228
|
+
prd.error %(The --expire value is not a number of hours: #{prd.fancy_ticks(options.expire)})
|
229
|
+
exit 1
|
230
|
+
end
|
231
|
+
# The platform expects the expiration time to be an integer
|
232
|
+
# representing microseconds since the epoch, e.g.,
|
233
|
+
# hours * mins/hour * secs/min * msec/sec * μsec/msec
|
234
|
+
# or hours * 60 * 60 * 1000 * 1000
|
235
|
+
micros_since_epoch = DateTime.now.strftime('%Q').to_i * 1000
|
236
|
+
mircos_until_purge = options.expire.to_i * 60 * 60 * 1000 * 1000
|
237
|
+
options.expire = micros_since_epoch + mircos_until_purge
|
238
|
+
end
|
239
|
+
|
240
|
+
unless options.auth.nil?
|
241
|
+
options.auth = options.auth.to_sym
|
242
|
+
unless MrMurano::Gateway::Device.DEVICE_AUTH_TYPES.include?(options.auth)
|
243
|
+
MrMurano::Verbose.error("unrecognized --auth: #{options.auth}")
|
244
|
+
exit 1
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
if !options.file.to_s.empty?
|
194
249
|
# Check file for headers.
|
195
|
-
|
250
|
+
begin
|
251
|
+
header = File.new(options.file).gets
|
252
|
+
rescue Errno::ENOENT => err
|
253
|
+
prd.error %(Unable to open file #{prd.fancy_ticks(options.file)}: #{err.message})
|
254
|
+
exit 2
|
255
|
+
end
|
196
256
|
if header.nil?
|
197
257
|
prd.error 'Nothing in file!'
|
198
258
|
exit 1
|
199
259
|
end
|
200
260
|
unless header =~ /\s*ID\s*(,SSL Client Certificate\s*)?/
|
201
|
-
prd.error
|
261
|
+
prd.error %(Missing column headers in file "#{options.file}")
|
202
262
|
prd.error %(First line in file should be either "ID" or "ID, SSL Client Certificate")
|
203
263
|
exit 2
|
204
264
|
end
|
205
|
-
prd.enable_batch(options.file)
|
265
|
+
prd.enable_batch(options.file, options.expire)
|
206
266
|
elsif args.count > 0
|
267
|
+
opts = {}
|
268
|
+
opts[:expire] = options.expire unless options.expire.nil?
|
269
|
+
opts[:type] = options.auth unless options.auth.nil?
|
207
270
|
if options.key
|
208
271
|
File.open(options.key, 'rb') do |io|
|
209
|
-
prd.enable(args[0],
|
272
|
+
prd.enable(args[0], **opts, key: io)
|
210
273
|
end
|
211
274
|
else
|
212
|
-
|
275
|
+
opts[:key] = options.cred unless options.cred.nil?
|
276
|
+
prd.enable(args[0], **opts)
|
213
277
|
end
|
214
278
|
else
|
215
|
-
|
279
|
+
# Impossible path: neither args nor --file; would've exited by now.
|
280
|
+
raise 'Impossible'
|
216
281
|
end
|
217
282
|
end
|
218
283
|
end
|
@@ -236,7 +301,7 @@ you cannot retrive the CIK again.
|
|
236
301
|
).strip
|
237
302
|
|
238
303
|
c.action do |args, _options|
|
239
|
-
c.verify_arg_count!(args, nil, ['
|
304
|
+
c.verify_arg_count!(args, nil, ['Missing device identifier'])
|
240
305
|
prd = MrMurano::Gateway::Device.new
|
241
306
|
prd.outf prd.activate(args.first)
|
242
307
|
end
|
@@ -250,7 +315,7 @@ Delete a device.
|
|
250
315
|
).strip
|
251
316
|
|
252
317
|
c.action do |args, _options|
|
253
|
-
c.verify_arg_count!(args, nil, ['
|
318
|
+
c.verify_arg_count!(args, nil, ['Missing device identifier'])
|
254
319
|
prd = MrMurano::Gateway::Device.new
|
255
320
|
snid = args.shift
|
256
321
|
ret = prd.remove(snid)
|
@@ -273,6 +338,53 @@ Get the URL for the HTTP-Data-API for this Project.
|
|
273
338
|
end
|
274
339
|
end
|
275
340
|
|
341
|
+
command 'device lock' do |c|
|
342
|
+
c.syntax = %(murano device lock <identifier>)
|
343
|
+
c.summary = %(Lock a device, not allowing connections to it until unlocked)
|
344
|
+
c.description = %(
|
345
|
+
Lock a device, not allowing connections to it until unlocked.
|
346
|
+
).strip
|
347
|
+
|
348
|
+
c.action do |args, _options|
|
349
|
+
c.verify_arg_count!(args, 1, ['Missing device identifier'])
|
350
|
+
prd = MrMurano::Gateway::Device.new
|
351
|
+
prd.lock(args[0])
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
command 'device unlock' do |c|
|
356
|
+
c.syntax = %(murano device unlock <identifier>)
|
357
|
+
c.summary = %(Unlock a device, allowing connections to it again)
|
358
|
+
c.description = %(
|
359
|
+
Unlock a device, allowing connections to it again.
|
360
|
+
).strip
|
361
|
+
|
362
|
+
c.action do |args, _options|
|
363
|
+
c.verify_arg_count!(args, 1, ['Missing device identifier'])
|
364
|
+
prd = MrMurano::Gateway::Device.new
|
365
|
+
prd.unlock(args[0])
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
command 'device revoke' do |c|
|
370
|
+
c.syntax = %(murano device revoke <identifier>)
|
371
|
+
c.summary = %(Force device to reprovision)
|
372
|
+
c.description = %(
|
373
|
+
Force device to reprovision.
|
374
|
+
|
375
|
+
This will revoke the device's keys and cause it to temporarily disconnect. The will then reconnect and be provisioned with new keys.
|
376
|
+
).strip
|
377
|
+
|
378
|
+
c.action do |args, _options|
|
379
|
+
c.verify_arg_count!(args, 1, ['Missing device identifier'])
|
380
|
+
prd = MrMurano::Gateway::Device.new
|
381
|
+
# MAYBE/2017-08-23: This command doesn't return an error if the device
|
382
|
+
# ID was not found, or if the keys were already revoked. Do we care?
|
383
|
+
# At least the lock command fails if the device ID is not found.
|
384
|
+
prd.revoke(args[0])
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
276
388
|
alias_command 'product device', 'device'
|
277
389
|
alias_command 'product device list', 'device list'
|
278
390
|
alias_command 'product devices list', 'device list'
|
@@ -283,4 +395,7 @@ alias_command 'product device enable', 'device enable'
|
|
283
395
|
alias_command 'product device activate', 'device activate'
|
284
396
|
alias_command 'product device delete', 'device delete'
|
285
397
|
alias_command 'product device httpurl', 'device httpurl'
|
398
|
+
alias_command 'product device lock', 'device lock'
|
399
|
+
alias_command 'product device unlock', 'device unlock'
|
400
|
+
alias_command 'product device revoke', 'device revoke'
|
286
401
|
|