MrMurano 1.7.4 → 1.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3d72a03a23fc127df24572d0bcce4bf5f8d9a09a
4
- data.tar.gz: 12f54ac0517fe13c75e27a00c183c281f96bf2a2
3
+ metadata.gz: 18a4696a79038b6f17c3a43cf1253edf00945062
4
+ data.tar.gz: aab88cf46593211db7207e8759e31f52a407a7c2
5
5
  SHA512:
6
- metadata.gz: 17203808b81805c17690858518445441aa2f16e3059ed35f3a1a1e6f13cf3697d9239c8b3424f6d678f367b0ef3a45345d377707dbb833f69971ae6ab753202b
7
- data.tar.gz: 817516675a6df0af3602ef3434f5faf36d00235f1bca4d650a3bf975042babce1eff4322fb7df598d4b0ead2b92996d71d0add9ec9f8004d24d19dc2fb15398c
6
+ metadata.gz: 1e88eb9c42ddfda7599c5be3f53cfec180f1faadb5fdc83a0d2f784401ba11eab1f54b5db69ac4d517f07a9284f1706502bbc7701989bcf8b52df5d39d4ecdff
7
+ data.tar.gz: 20751f37d098dbc3f547518c80a9a465eaa62b9850b60ce12089f1b661c7ded530f68391e537994513cd0762d292b45c6277b11e078069d0bcce92b10b5f4c59
@@ -5,6 +5,13 @@
5
5
 
6
6
  Do more from the command line with [Murano](https://exosite.com/platform/)
7
7
 
8
+ MrMurano is the command-line tool that interacts with Murano and makes different
9
+ tasks easier. MrMurano makes it easy to deploy code to a solution, import many
10
+ product definitions at once, set up endpoints and APIs, and more.
11
+
12
+ MrMurano works around the idea of syncing, much like rsync. Files from your working
13
+ directory are synced up (or down) from Murano.
14
+
8
15
  ## Usage
9
16
 
10
17
  ### To start from an existing project in Murano
@@ -26,11 +33,13 @@ running in Murano. Here is the list.
26
33
  - Set it: `mr config business.id ZZZZZZZZZ`
27
34
  - Create a product: `mr product create myawesomeproduct`
28
35
  - Save the result: `mr config product.id YYYYYYYYY`
29
- - Define the product: `mr product spec push --file prd.spec`
36
+ - Set the product definition: `mr config product.spec prd.spec`
37
+ - Add resource aliases to specs/prd.spec
38
+ - Sync the product definition up: `mr syncup -V --specs`
30
39
  - Create a solution: `mr solution create myawesomesolution`
31
40
  - Save the result: `mr config solution.id XXXXXX`
32
41
  - Sync solution code up: `mr syncup -V`
33
- - Assign the prodcut to the solution: `mr assign set`
42
+ - Assign the product to the solution: `mr assign set`
34
43
 
35
44
  Do stuff, see what changed: `mr status` or `mr diff`.
36
45
  Then deploy with `mr syncup`
@@ -105,15 +114,29 @@ EOF
105
114
  This also allows for keeping private things in a seperate config file and having
106
115
  the shared things checked into source control.
107
116
 
108
- ### Keystore
117
+ ### Direct Service Access
118
+
119
+ To aid with debugging, MrMurano has direct access to some of the services in a
120
+ solution.
109
121
 
110
- To aid with debugging, MrMurano has direct access to a solution's Keystore service.
122
+ Currently these are:
123
+ - Keystore: `mr keystore`
124
+ - Timeseries: `mr timeseries`
125
+ - TSDB: `mr tsdb`
111
126
 
112
- To see all of the keys in the current solution: `mr keystore`
127
+ ### Output Format
113
128
 
114
- ### Timeseries
129
+ Many sub-commands respect the `outformat` setting. This lets you switch the output
130
+ between YAML, JSON, Ruby, CSV, and pretty tables. Not all formats work with all
131
+ commands.
115
132
 
116
- To aid with debugging, MrMurano has direct access to a solution's Timeseries service.
133
+ ```
134
+ mr tsdb product list
135
+ mr tsdb product list -c outformat=csv
136
+ mr tsdb product list -c outformat=json
137
+ mr tsdb product list -c outformat=yaml
138
+ mr tsdb product list -c outformat=pp
139
+ ```
117
140
 
118
141
  ### Product Content Area
119
142
 
@@ -183,18 +206,9 @@ spec
183
206
  spec/cico.murano.spec
184
207
  ```
185
208
 
209
+ ## Developing
186
210
 
187
- ### Bundles
188
-
189
- MrMuanro allows adding bundles of resources to your project.
190
-
191
- A Bundle is a group of modules, endpoints, and static files.
192
-
193
- Bundles live in the 'bundle' directory. Each bundle is a directory that matches
194
- the layout of a project. (with directories for endpoints, modules, files, etc)
195
-
196
- The items in bundles are layered by sorting the bundle names. Then your project's
197
- items are layered on top. This builds the list of what is synced. It also allows
198
- you to override things that are in a bundle from you project.
211
+ MrMurano uses git flow for managing branches.
199
212
 
213
+ MrMurano also uses [bunder](http://bundler.io).
200
214
 
@@ -3,6 +3,12 @@ Readme:
3
3
  - Look into using VCR for testing. @pri(low)
4
4
 
5
5
  Commands:
6
+ - Init command.
7
+ - Empty sub-commands should return help. @done(2016-11-21)
8
+ There are a bunch of empty sub-commands that prefix another layer. Such as
9
+ assign, content, product, and others. Those should be impemented as a ‘help’
10
+ only command. That is they should return help like the plain `mr` command, but
11
+ just for their sub-section of things
6
12
  - Errors and Warnings should get sent to STDERR @done(2016-11-03)
7
13
  - Need a more consistent output format. 'pp' is still used in many places. @done(2016-11-03)
8
14
  Maybe have a tool setting for output format? json, yaml, pp, csv, table ?
@@ -17,7 +23,7 @@ Account:
17
23
  - Netrc library (or the netrc format) doesn't allow '#' in passwords. @done(2016-08-10)
18
24
 
19
25
  Endpoints:
20
- - Add support for multiple endpoints in one file (maybe) @pri(low)
26
+ - Add support for multiple endpoints in one file @pri(high) @done(2016-11-18)
21
27
  - Add directory support like in modules @done(2016-07-26)
22
28
 
23
29
  Files:
@@ -39,12 +45,18 @@ CORS:
39
45
  - GET&PUT /cors data @done(2016-09-08)
40
46
 
41
47
  TSDB:
42
- - Add support for new TSDB service. @pri(high)
48
+ - For query, if no metrics on cmdline, then do listMetrics and use all. @done(2016-11-21)
49
+ well, the first 1000 or whatever we get from a single call to listMetrics
50
+ - Query should handle tags prefixed with @ to match write. @done(2016-11-21)
51
+ - Add support for new TSDB service. @pri(high) @done(2016-11-03)
43
52
 
44
53
  Timeseries:
45
54
  - Add CSV output option. @done(2016-09-09)
46
55
 
47
56
  Product:
57
+ - Support multiple products.
58
+ Think about how this would work. There is the syncing of the resoruces, and then
59
+ some of the commands that use product.id.
48
60
  - Auto convert exoline spec files into murano spec files on upload? @done(2016-10-27)
49
61
  Not doing this. Convert is there if you need it.
50
62
  - write alias command @done(2016-09-26)
@@ -54,12 +66,20 @@ Service Device:
54
66
  - When listing and bussiness.id is missing, gracefully fall back to --idonly @done(2016-09-12)
55
67
 
56
68
  Config:
69
+ - Add config tool.default_sync to set which things sync{up,down} by default
70
+ It is internally hardcoded to be -s, -a, -m, -e right now.
57
71
  - Add ENV['MR_CONFIGFILE'] path to file to load like --configfile @done(2016-09-22)
58
72
  - Maybe add dotenv support. @done(2016-09-22)
59
73
  - Think about adding dev,staging,prod system; how would that work? @done(2016-09-16)
60
74
 
75
+ SyncUpDown:
76
+ - Document the hash keys for an item. @pri(high)
77
+ Also consider turning that hash into a Struct
78
+ - Allow specifying local files to limit actions to.
79
+
61
80
  SolutionBase:
62
81
  - All network traffic is serialized. Make some parallel.
82
+ This might break some things.
63
83
  - Errors from the server should be displayed prettier. @done(2016-09-26)
64
84
  - JSON parse should use symbols for keys. @done(2016-09-01)
65
85
  - Add the --curl verbose option. @done(2016-08-12)
@@ -39,7 +39,7 @@ module MrMurano
39
39
  CFG_PRVT_NAME = '.mrmuranorc.private'.freeze # Going away.
40
40
  CFG_DIR_NAME = '.mrmurano'.freeze
41
41
  CFG_ALTRC_NAME = '.mrmurano/config'.freeze
42
- CFG_SYS_NAME = '/etc/mrmuranorc'.freeze
42
+ CFG_SYS_NAME = '/etc/mrmuranorc'.freeze # Going away.
43
43
 
44
44
  def initialize
45
45
  @paths = []
@@ -64,7 +64,10 @@ module MrMurano
64
64
  end
65
65
  @paths << ConfigFile.new(:user, Pathname.new(Dir.home) + CFG_FILE_NAME)
66
66
  fixModes(Pathname.new(Dir.home) + CFG_DIR_NAME)
67
- @paths << ConfigFile.new(:system, Pathname.new(CFG_SYS_NAME))
67
+ if Pathname.new(CFG_SYS_NAME).exist? then
68
+ say_warning "!!! Using #{CFG_SYS_NAME} is deprecated"
69
+ @paths << ConfigFile.new(:system, Pathname.new(CFG_SYS_NAME))
70
+ end
68
71
  @paths << ConfigFile.new(:defaults, nil, IniFile.new())
69
72
 
70
73
 
@@ -83,6 +86,7 @@ module MrMurano
83
86
  set('location.roles', 'roles.yaml', :defaults)
84
87
  set('location.users', 'users.yaml', :defaults)
85
88
  set('location.cors', 'cors.yaml', :defaults)
89
+ set('location.specs', 'specs', :defaults)
86
90
 
87
91
  set('files.default_page', 'index.html', :defaults)
88
92
 
@@ -33,10 +33,12 @@ module MrMurano
33
33
  name = $cfg['product.spec']
34
34
  prid = $cfg['product.id']
35
35
  name = $cfg["p-#{prid}.spec"] unless prid.nil? or $cfg["p-#{prid}.spec"].nil?
36
+ raise "No spec file named; run `mr config prodcut.spec <specfile>`" if name.nil?
36
37
 
37
38
  unless $cfg['location.specs'].nil? then
38
- name = File.join($cfg['location.specs'], name)
39
+ name = ::File.join($cfg['location.specs'], name)
39
40
  end
41
+ debug " spec file name => #{name}"
40
42
  name
41
43
  end
42
44
 
@@ -16,11 +16,15 @@ module MrMurano
16
16
  ##
17
17
  # This gets all data about all endpoints
18
18
  def list
19
- get()
19
+ get().map do |item|
20
+ item[:content_type] = 'application/json' if item[:content_type].empty?
21
+ item
22
+ end
20
23
  end
21
24
 
22
25
  def fetch(id)
23
26
  ret = get('/' + id.to_s)
27
+ ret[:content_type] = 'application/json' if ret[:content_type].empty?
24
28
  aheader = (ret[:script].lines.first or "").chomp
25
29
  dheader = /^--#ENDPOINT (?i:#{ret[:method]}) #{ret[:path]}$/
26
30
  rheader = %{--#ENDPOINT #{ret[:method]} #{ret[:path]}\n}
@@ -45,10 +49,12 @@ module MrMurano
45
49
  raise "no file" unless local.exist?
46
50
 
47
51
  # we assume these are small enough to slurp.
48
- script = local.read
52
+ unless remote.has_key? :script then
53
+ script = local.read
54
+ remote[:script] = script
55
+ end
49
56
  limitkeys = [:method, :path, :script, @itemkey]
50
57
  remote = remote.select{|k,v| limitkeys.include? k }
51
- remote[:script] = script
52
58
  # post('', remote)
53
59
  if remote.has_key? @itemkey then
54
60
  put('/' + remote[@itemkey], remote) do |request, http|
@@ -84,16 +90,31 @@ module MrMurano
84
90
  end
85
91
 
86
92
  def toRemoteItem(from, path)
87
- # read first line of file and get method/path from it?
93
+ # Path could be have multiple endpoints in side, so a loop.
94
+ items = []
88
95
  path = Pathname.new(path) unless path.kind_of? Pathname
89
- aheader = path.readlines().first
90
- md = /--#ENDPOINT (\S+) (.*)/.match(aheader)
91
- if md.nil? then
92
- rp = path.relative_path_from(Pathname.new(Dir.pwd))
93
- say_warning "Not an Endpoint: #{rp.to_s}"
94
- return nil
96
+ cur = nil
97
+ lineno=0
98
+ path.readlines().each do |line|
99
+ md = /--#ENDPOINT (?<method>\S+) (?<path>\S+)( (?<ctype>.*))?/.match(line)
100
+ if not md.nil? then
101
+ # header line.
102
+ cur[:line_end] = lineno unless cur.nil?
103
+ items << cur unless cur.nil?
104
+ cur = {:method=>md[:method],
105
+ :path=>md[:path],
106
+ :content_type=> (md[:ctype] or 'application/json'),
107
+ :local_path=>path,
108
+ :line=>lineno,
109
+ :script=>line}
110
+ elsif not cur.nil? and not cur[:script].nil? then
111
+ cur[:script] << line
112
+ end
113
+ lineno += 1
95
114
  end
96
- {:method=>md[1], :path=>md[2]}
115
+ cur[:line_end] = lineno unless cur.nil?
116
+ items << cur unless cur.nil?
117
+ items
97
118
  end
98
119
 
99
120
  def synckey(item)
@@ -107,7 +128,7 @@ module MrMurano
107
128
  if itemB[:script].nil? and itemB[:local_path] then
108
129
  itemB[:script] = itemB[:local_path].read
109
130
  end
110
- return itemA[:script] != itemB[:script]
131
+ return (itemA[:script] != itemB[:script] or itemA[:content_type] != itemB[:content_type])
111
132
  end
112
133
 
113
134
  end
@@ -37,6 +37,7 @@ module MrMurano
37
37
 
38
38
  def call(opid, meth=:get, data=nil, id=scid, &block)
39
39
  call = "/#{id.to_s}/call/#{opid.to_s}"
40
+ debug "Will call: #{call}"
40
41
  case meth
41
42
  when :get
42
43
  get(call, data, &block)
@@ -0,0 +1,45 @@
1
+
2
+ module MrMurano
3
+ class SubCmdGroupHelp
4
+ attr :name, :description
5
+
6
+ def initialize(command)
7
+ @name = command.syntax.to_s
8
+ @description = command.description.to_s
9
+ @runner = ::Commander::Runner.instance
10
+ prefix = /^#{command.name.to_s} /
11
+ cmds = @runner.instance_variable_get(:@commands).select{|n,_| n.to_s =~ prefix}
12
+ @commands = cmds
13
+ als = @runner.instance_variable_get(:@aliases).select{|n,_| n.to_s =~ prefix}
14
+ @aliases = als
15
+
16
+ @options = {}
17
+ end
18
+
19
+ def program(key)
20
+ case key
21
+ when :name
22
+ @name
23
+ when :description
24
+ @description
25
+ when :help
26
+ nil
27
+ else
28
+ nil
29
+ end
30
+ end
31
+
32
+ def alias?(name)
33
+ @aliases.include? name.to_s
34
+ end
35
+
36
+ def get_help
37
+ hf = @runner.program(:help_formatter).new(self)
38
+ pc = Commander::HelpFormatter::ProgramContext.new(self).get_binding
39
+ hf.template(:help).result(pc)
40
+ end
41
+ end
42
+ end
43
+
44
+
45
+ # vim: set ai et sw=2 ts=2 :
@@ -5,6 +5,7 @@ require 'MrMurano/Config'
5
5
  require 'MrMurano/hash'
6
6
 
7
7
  module MrMurano
8
+ ## Track what things are syncable.
8
9
  class SyncRoot
9
10
  Syncable = Struct.new(:name, :class, :type, :desc, :bydefault) do
10
11
  end
@@ -258,16 +259,18 @@ module MrMurano
258
259
  path
259
260
  end
260
261
  end.flatten.compact.reject do |path|
262
+ # TODO: These globs should be in $cfg.
261
263
  path.fnmatch('*_test.lua') or path.basename.fnmatch('.*')
262
264
  end.select do |path|
265
+ # TODO: These globs should be in $cfg.
263
266
  path.extname == '.lua'
264
267
  end.map do |path|
265
- # sometimes this is a name, sometimes it is an item.
266
- # do I want to keep that? NO.
267
- name = toRemoteItem(from, path)
268
- unless name.nil? then
269
- name[:local_path] = path
270
- name
268
+ item = toRemoteItem(from, path)
269
+ if item.kind_of?(Array) then
270
+ item.compact.map{|i| i[:local_path] = path; i}
271
+ elsif not item.nil? then
272
+ item[:local_path] = path
273
+ item
271
274
  end
272
275
  end.flatten.compact
273
276
  end
@@ -368,19 +371,31 @@ module MrMurano
368
371
  # @param item Hash: The item to get a diff of
369
372
  # @return String: The diff output
370
373
  def dodiff(item)
371
- tfp = Tempfile.new([tolocalname(item, @itemkey), '.lua'])
374
+ trmt = Tempfile.new([tolocalname(item, @itemkey)+'_remote_', '.lua'])
375
+ tlcl = Tempfile.new([tolocalname(item, @itemkey)+'_local_', '.lua'])
376
+ if item.has_key? :script then
377
+ Pathname.new(tlcl.path).open('wb') do |io|
378
+ io << item[:script]
379
+ end
380
+ else
381
+ Pathname.new(tlcl.path).open('wb') do |io|
382
+ io << item[:local_path].read
383
+ end
384
+ end
372
385
  df = ""
373
386
  begin
374
- download(Pathname.new(tfp.path), item)
387
+ download(Pathname.new(trmt.path), item)
375
388
 
376
389
  cmd = $cfg['diff.cmd'].shellsplit
377
- cmd << tfp.path
378
- cmd << item[:local_path].to_s
390
+ cmd << trmt.path
391
+ cmd << tlcl.path
379
392
 
380
393
  IO.popen(cmd) {|io| df = io.read }
381
394
  ensure
382
- tfp.close
383
- tfp.unlink
395
+ trmt.close
396
+ trmt.unlink
397
+ tlcl.close
398
+ tlcl.unlink
384
399
  end
385
400
  df
386
401
  end
@@ -7,12 +7,14 @@ require 'MrMurano/commands/domain'
7
7
  require 'MrMurano/commands/exportImport'
8
8
  require 'MrMurano/commands/keystore'
9
9
  require 'MrMurano/commands/logs'
10
+ require 'MrMurano/commands/product'
10
11
  require 'MrMurano/commands/productCreate'
11
12
  require 'MrMurano/commands/productDelete'
12
13
  require 'MrMurano/commands/productList'
13
14
  require 'MrMurano/commands/productSpec'
14
15
  require 'MrMurano/commands/productWrite'
15
16
  require 'MrMurano/commands/serialNumberCmds'
17
+ require 'MrMurano/commands/solution'
16
18
  require 'MrMurano/commands/solutionCreate'
17
19
  require 'MrMurano/commands/solutionDelete'
18
20
  require 'MrMurano/commands/solutionList'
@@ -4,11 +4,14 @@ command 'assign list' do |c|
4
4
  c.syntax = 'mr assign list [options]'
5
5
  c.description = 'List the products that are assigned'
6
6
  c.option '--idonly', 'Only return the ids'
7
+
7
8
  c.action do |args, options|
8
9
  sol = MrMurano::SC_Device.new
9
10
 
10
11
  trigs = sol.showTriggers()
11
- if options.idonly or $cfg['business.id'].nil? then
12
+ options.idonly = true if $cfg['business.id'].nil?
13
+
14
+ if options.idonly then
12
15
  say trigs.join(' ')
13
16
  else
14
17
  acc = MrMurano::Account.new
@@ -17,8 +20,8 @@ command 'assign list' do |c|
17
20
  if products.empty? then
18
21
  say trigs.join(' ')
19
22
  else
20
- busy = products.map{|r| [r[:label], r[:type], r[:pid], r[:modelId]]}
21
- table = Terminal::Table.new :rows => busy, :headings => ['Label', 'Type', 'PID', 'ModelID']
23
+ busy = products.map{|r| [r[:label], r[:modelId]]}
24
+ table = Terminal::Table.new :rows => busy, :headings => ['Label', 'ModelID']
22
25
  say table
23
26
  end
24
27
  end
@@ -1,5 +1,19 @@
1
1
  require 'MrMurano/Product'
2
2
 
3
+ command :content do |c|
4
+ c.syntax = %{mr content}
5
+ c.summary = %{About Content Area}
6
+ c.description = %{This set of commands let you interact with the content area for a product.
7
+
8
+ This is where OTA data can be stored so that devices can easily download it.
9
+ }
10
+
11
+ c.action do |args, options|
12
+ ::Commander::UI.enable_paging
13
+ say MrMurano::SubCmdGroupHelp.new(c).get_help
14
+ end
15
+ end
16
+
3
17
  command 'content list' do |c|
4
18
  c.syntax = %{mr content list}
5
19
  c.summary = %{List downloadable content for a product}
@@ -13,7 +27,6 @@ command 'content list' do |c|
13
27
  prd.outf prd.list
14
28
  end
15
29
  end
16
- alias_command :content, 'content list'
17
30
 
18
31
  command 'content info' do |c|
19
32
  c.syntax = %{mr content info <content id>}
@@ -36,6 +36,18 @@ module MrMurano
36
36
  end
37
37
  end
38
38
 
39
+ command :keystore do |c|
40
+ c.syntax = %{mr keystore}
41
+ c.summary = %{About Keystore}
42
+ c.description = %{The Keystore sub-commands let you interact directly with the Keystore instance
43
+ in a solution. This allows for easier debugging, being able to quickly get and
44
+ set data. As well as calling any of the other supported REDIS commands.}
45
+ c.action do |args, options|
46
+ ::Commander::UI.enable_paging
47
+ say MrMurano::SubCmdGroupHelp.new(c).get_help
48
+ end
49
+ end
50
+
39
51
  command 'keystore info' do |c|
40
52
  c.syntax = %{mr keystore info}
41
53
  c.description = %{Show info about the Keystore}
@@ -53,7 +65,6 @@ command 'keystore list' do |c|
53
65
  sol.outf sol.listkeys
54
66
  end
55
67
  end
56
- alias_command :keystore, 'keystore list'
57
68
 
58
69
  command 'keystore get' do |c|
59
70
  c.syntax = %{mr keystore get <key>}
@@ -0,0 +1,14 @@
1
+ require 'MrMurano/SubCmdGroupContext'
2
+
3
+ command :product do |c|
4
+ c.syntax = %{mr product}
5
+ c.summary = %{About Product}
6
+ c.description = %{Sub-commands for working with Products}
7
+
8
+ c.action do |args, options|
9
+ ::Commander::UI.enable_paging
10
+ say MrMurano::SubCmdGroupHelp.new(c).get_help
11
+ end
12
+ end
13
+
14
+ # vim: set ai et sw=2 ts=2 :
@@ -5,19 +5,42 @@ command 'product list' do |c|
5
5
  c.syntax = %{mr product list [options]}
6
6
  c.description = %{List products}
7
7
  c.option '--idonly', 'Only return the ids'
8
+ c.option '--[no-]all', 'Show all fields'
9
+ c.option '-o', '--output FILE', %{Download to file instead of STDOUT}
8
10
 
9
11
  c.action do |args, options|
10
12
  acc = MrMurano::Account.new
11
13
  data = acc.products
14
+
15
+ io=nil
16
+ if options.output then
17
+ io = File.open(options.output, 'w')
18
+ end
19
+
12
20
  if options.idonly then
13
- say data.map{|row| row[:pid]}.join(' ')
21
+ headers = [:modelId]
22
+ data = data.map{|row| [row[:modelId]]}
23
+ elsif not options.all then
24
+ headers = [:label, :modelId]
25
+ data = data.map{|r| [r[:label], r[:modelId]]}
14
26
  else
15
- busy = data.map{|r| [r[:label], r[:type], r[:pid], r[:modelId]]}
16
- table = Terminal::Table.new :rows => busy, :headings => ['Label', 'Type', 'PID', 'ModelID']
17
- say table
27
+ headers = data[0].keys
28
+ data = data.map{|r| headers.map{|h| r[h]}}
18
29
  end
30
+
31
+ acc.outf(data, io) do |dd, ios|
32
+ if options.idonly then
33
+ ios.puts dd.join(' ')
34
+ else
35
+ acc.tabularize({
36
+ :headers=>headers.map{|h| h.to_s},
37
+ :rows=>dd
38
+ }, ios)
39
+ end
40
+ end
41
+ io.close unless io.nil?
42
+
19
43
  end
20
44
  end
21
- alias_command :product, 'product list'
22
45
 
23
46
  # vim: set ai et sw=2 ts=2 :
@@ -4,6 +4,17 @@ require 'MrMurano/hash'
4
4
  require 'yaml'
5
5
  require 'terminal-table'
6
6
 
7
+ command 'product spec' do |c|
8
+ c.syntax = %{mr product spec}
9
+ c.summary = %{About Product Specs}
10
+ c.description = %{Some utility for working with prodcut specification files.}
11
+
12
+ c.action do |args, options|
13
+ ::Commander::UI.enable_paging
14
+ say MrMurano::SubCmdGroupHelp.new(c).get_help
15
+ end
16
+ end
17
+
7
18
  command 'product spec convert' do |c|
8
19
  c.syntax = %{mr product spec convert FILE}
9
20
  c.summary = %{Convert exoline spec file into Murano format}
@@ -72,11 +83,11 @@ command 'product spec pull' do |c|
72
83
  end
73
84
 
74
85
  prd.outf(ret, io) do |dd, ios|
75
- if ret.kind_of? Array then
76
- ios.puts ret.join(' ')
86
+ if dd.kind_of? Array then
87
+ ios.puts dd.join(' ')
77
88
  else
78
89
  prd.tabularize({
79
- :rows => ret[:resources].map{|r| [r[:alias], r[:format], r[:rid]]},
90
+ :rows => dd[:resources].map{|r| [r[:alias], r[:format], r[:rid]]},
80
91
  :headers => ['Alias', 'Format', 'RID']
81
92
  }, ios)
82
93
  end
@@ -84,7 +95,6 @@ command 'product spec pull' do |c|
84
95
  io.close unless io.nil?
85
96
  end
86
97
  end
87
- alias_command 'product spec', 'product spec pull'
88
98
  alias_command 'product spec list', 'product spec pull', '--astable'
89
99
 
90
100
  # vim: set ai et sw=2 ts=2 :
@@ -1,6 +1,18 @@
1
1
  require 'MrMurano/Product'
2
2
  require 'terminal-table'
3
3
 
4
+ command :sn do |c|
5
+ c.syntax = %{mr sn}
6
+ c.summary = %{About Serial Numbers}
7
+ c.description = %{The sn sub-commands allow for managing the identifiers (or Serial Numbers) on
8
+ a product.}
9
+
10
+ c.action do |args, options|
11
+ ::Commander::UI.enable_paging
12
+ say MrMurano::SubCmdGroupHelp.new(c).get_help
13
+ end
14
+ end
15
+
4
16
  command 'sn list' do |c|
5
17
  c.syntax = %{mr sn list [options]}
6
18
  c.summary = %{List serial numbers for a product}
@@ -18,7 +30,6 @@ command 'sn list' do |c|
18
30
  say table
19
31
  end
20
32
  end
21
- alias_command :sn, 'sn list'
22
33
 
23
34
  command 'sn enable' do |c|
24
35
  c.syntax = %{mr sn enable [<sn>|--file <sns>]}
@@ -0,0 +1,14 @@
1
+ require 'MrMurano/SubCmdGroupContext'
2
+
3
+ command :solution do |c|
4
+ c.syntax = %{mr solution}
5
+ c.summary = %{About Solution}
6
+ c.description = %{Sub-commands for working with solutions}
7
+
8
+ c.action do |args, options|
9
+ ::Commander::UI.enable_paging
10
+ say MrMurano::SubCmdGroupHelp.new(c).get_help
11
+ end
12
+ end
13
+
14
+ # vim: set ai et sw=2 ts=2 :
@@ -5,19 +5,41 @@ command 'solution list' do |c|
5
5
  c.syntax = %{mr solution list [options]}
6
6
  c.description = %{List solutions}
7
7
  c.option '--idonly', 'Only return the ids'
8
+ c.option '--[no-]all', 'Show all fields'
9
+ c.option '-o', '--output FILE', %{Download to file instead of STDOUT}
8
10
 
9
11
  c.action do |args, options|
10
12
  acc = MrMurano::Account.new
11
13
  data = acc.solutions
14
+
15
+ io=nil
16
+ if options.output then
17
+ io = File.open(options.output, 'w')
18
+ end
19
+
12
20
  if options.idonly then
13
- say data.map{|row| row[:apiId]}.join(' ')
21
+ headers = [:apiId]
22
+ data = data.map{|row| [row[:apiId]]}
23
+ elsif not options.all then
24
+ headers = [:apiId, :domain]
25
+ data = data.map{|r| [r[:apiId], r[:domain]]}
14
26
  else
15
- busy = data.map{|r| [r[:apiId], r[:domain], r[:type], r[:sid]]}
16
- table = Terminal::Table.new :rows => busy, :headings => ['API ID', 'Domain', 'Type', 'SID']
17
- say table
27
+ headers = data[0].keys
28
+ data = data.map{|r| headers.map{|h| r[h]}}
29
+ end
30
+
31
+ acc.outf(data, io) do |dd, ios|
32
+ if options.idonly then
33
+ ios.puts dd.join(' ')
34
+ else
35
+ acc.tabularize({
36
+ :headers=>headers.map{|h| h.to_s},
37
+ :rows=>dd
38
+ }, ios)
39
+ end
18
40
  end
41
+ io.close unless io.nil?
19
42
  end
20
43
  end
21
- alias_command :solution, 'solution list'
22
44
 
23
45
  # vim: set ai et sw=2 ts=2 :
@@ -20,7 +20,11 @@ command :status do |c|
20
20
 
21
21
  def fmtr(item)
22
22
  if item.has_key? :local_path then
23
- item[:local_path].relative_path_from(Pathname.pwd()).to_s
23
+ lp = item[:local_path].relative_path_from(Pathname.pwd()).to_s
24
+ if item.has_key?(:line) and item[:line] > 0 then
25
+ return "#{lp}:#{item[:line]}"
26
+ end
27
+ lp
24
28
  else
25
29
  item[:synckey]
26
30
  end
@@ -22,6 +22,19 @@ module MrMurano
22
22
  end
23
23
  end
24
24
 
25
+ command :timeseries do |c|
26
+ c.syntax = %{mr timeseries}
27
+ c.summary = %{About Timeseries}
28
+ c.description = %{The timeseries sub-commands let you interact directly with the Timeseries
29
+ instance in a solution. This allows for easier debugging, being able to
30
+ quickly try out different queries or write test data.}
31
+
32
+ c.action do |args, options|
33
+ ::Commander::UI.enable_paging
34
+ say MrMurano::SubCmdGroupHelp.new(c).get_help
35
+ end
36
+ end
37
+
25
38
  command 'timeseries query' do |c|
26
39
  c.syntax = %{mr timeseries query <query string>}
27
40
  c.description = %{Query the timeseries database}
@@ -1,5 +1,6 @@
1
1
  require 'date'
2
2
  require 'MrMurano/Solution-ServiceConfig'
3
+ require 'MrMurano/SubCmdGroupContext'
3
4
 
4
5
  module MrMurano
5
6
  module ServiceConfigs
@@ -50,6 +51,7 @@ Also, many date-time formats can be parsed and will be converted to microseconds
50
51
  }
51
52
  c.option '--when TIMESTAMP', %{When this data happened. (defaults to now)}
52
53
  # TODO: add option to take data from STDIN.
54
+ c.example 'mr tsdb write hum=45 lux=12765 @sn=44', %{Write two metrics (hum and lux) with a tag (sn)}
53
55
 
54
56
  c.action do |args, options|
55
57
  sol = MrMurano::ServiceConfigs::Tsdb.new
@@ -80,15 +82,13 @@ Also, many date-time formats can be parsed and will be converted to microseconds
80
82
  end
81
83
 
82
84
  command 'tsdb query' do |c|
83
- c.syntax = %{mr tsdb query [options] }
85
+ c.syntax = %{mr tsdb query [options] <metric>|@<tag=value> …}
84
86
  c.summary = %{query data}
85
- c.description =%{
86
-
87
- list metrics to return
88
- list tag=value to match
89
-
87
+ c.description =%{Query data from the TSDB.
90
88
 
91
89
  FUNCS is a comma seperated list of the aggregate functions.
90
+ Currently: avg, min, max, count, sum. For string metrics, only count.
91
+
92
92
  FILL is null, none, any integer, previous
93
93
 
94
94
  DURATION is an integer with time unit to indicate relative time before now.
@@ -114,6 +114,16 @@ Also, many date-time formats can be parsed and will be converted to microseconds
114
114
 
115
115
  c.option '-o', '--output FILE', %{Download to file instead of STDOUT}
116
116
 
117
+ c.example 'mr tsdb query hum', 'Get all hum metric entries'
118
+ c.example 'mr tsdb query hum @sn=45', 'Get all hum metric entries for tag sn=45'
119
+ c.example 'mr tsdb query hum --limit 1', 'Get just the most recent entry'
120
+ c.example 'mr tsdb query hum --relative_start 1h', 'Get last hour of hum entries'
121
+ c.example 'mr tsdb query hum --relative_start -1h', 'Get last hour of hum entries'
122
+ c.example 'mr tsdb query hum --relative_start 2h --relative_end 1h', 'Get hum entries of two hours ago, but not the last hours'
123
+ c.example 'mr tsdb query hum --sampling_size 30m', 'Get one hum entry from each 30 minute chunk of time'
124
+ c.example 'mr tsdb query hum --sampling_size 30m --aggregate avg', 'Get average hum entry from each 30 minute chunk of time'
125
+
126
+
117
127
  c.action do |args, options|
118
128
  sol = MrMurano::ServiceConfigs::Tsdb.new
119
129
 
@@ -124,6 +134,7 @@ Also, many date-time formats can be parsed and will be converted to microseconds
124
134
  if arg =~ /=/ then
125
135
  # a tag.
126
136
  k,v = arg.split('=', 2)
137
+ k = k[1..-1] if k[0] == '@'
127
138
  tags[k] = v
128
139
  else
129
140
  metrics << arg
@@ -132,6 +143,11 @@ Also, many date-time formats can be parsed and will be converted to microseconds
132
143
  query[:tags] = tags unless tags.empty?
133
144
  query[:metrics] = metrics unless metrics.empty?
134
145
 
146
+ # A query without any metrics is invalid. So if the user didn't provide any,
147
+ # look up all of them (well, frist however many) and use that list.
148
+ ret = sol.listMetrics
149
+ query[:metrics] = ret[:metrics]
150
+
135
151
  unless options.start_time.nil? then
136
152
  query[:start_time] = sol.str_to_timestamp(options.start_time)
137
153
  end
@@ -199,4 +215,17 @@ command 'tsdb list metrics' do |c|
199
215
  end
200
216
  end
201
217
 
218
+ command :tsdb do |c|
219
+ c.syntax = %{mr tsdb}
220
+ c.summary = %{About TSDB}
221
+ c.description = %{The tsdb sub-commands let you interact directly with the TSDB instance in a
222
+ solution. This allows for easier debugging, being able to quickly try out
223
+ different queries or write test data.}
224
+
225
+ c.action do |args, options|
226
+ ::Commander::UI.enable_paging
227
+ say MrMurano::SubCmdGroupHelp.new(c).get_help
228
+ end
229
+ end
230
+
202
231
  # vim: set ai et sw=2 ts=2 :
@@ -1,4 +1,4 @@
1
1
  module MrMurano
2
- VERSION = '1.7.4'.freeze
2
+ VERSION = '1.8.0'.freeze
3
3
  end
4
4
 
@@ -8,6 +8,7 @@ RSpec.describe MrMurano::ProductResources do
8
8
  $cfg.load
9
9
  $cfg['net.host'] = 'bizapi.hosted.exosite.io'
10
10
  $cfg['product.id'] = 'XYZ'
11
+ $cfg['product.spec'] = 'XYZ.yaml'
11
12
 
12
13
  @prd = MrMurano::ProductResources.new
13
14
  allow(@prd).to receive(:token).and_return("TTTTTTTTTT")
@@ -19,6 +20,37 @@ RSpec.describe MrMurano::ProductResources do
19
20
  expect(uri.to_s).to eq("https://bizapi.hosted.exosite.io/api:1/product/XYZ/proxy/onep:v1/rpc/process")
20
21
  end
21
22
 
23
+ context "location" do
24
+ it "Gets a product.spec, with location.specs" do
25
+ loc = @prd.location
26
+ expect(loc).to eq("specs/XYZ.yaml")
27
+ end
28
+ it "Gets a product.spec, without location.specs" do
29
+ $cfg.set('location.specs', nil, :defaults)
30
+ loc = @prd.location
31
+ expect(loc).to eq("XYZ.yaml")
32
+ end
33
+
34
+ it "Gets a p-FOO.spec, with location.specs" do
35
+ $cfg['p-XYZ.spec'] = 'magical.file'
36
+ loc = @prd.location
37
+ expect(loc).to eq("specs/magical.file")
38
+ end
39
+
40
+ it "Gets a p-FOO.spec, without location.specs" do
41
+ $cfg['p-XYZ.spec'] = 'magical.file'
42
+ $cfg.set('location.specs', nil, :defaults)
43
+ loc = @prd.location
44
+ expect(loc).to eq("magical.file")
45
+ end
46
+
47
+ it "raises when no spec name" do
48
+ $cfg['product.spec'] = nil
49
+ $cfg['product.id'] = nil
50
+ expect { @prd.location }.to raise_error("No spec file named; run `mr config prodcut.spec <specfile>`")
51
+ end
52
+ end
53
+
22
54
  context "do_rpc" do
23
55
  # Note, do_rpc is private.
24
56
  it "Accepts an object" do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: MrMurano
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.4
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Conrad Tadpol Tilstra
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-11-11 00:00:00.000000000 Z
11
+ date: 2016-11-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: commander
@@ -209,6 +209,7 @@ files:
209
209
  - lib/MrMurano/Solution-Services.rb
210
210
  - lib/MrMurano/Solution-Users.rb
211
211
  - lib/MrMurano/Solution.rb
212
+ - lib/MrMurano/SubCmdGroupContext.rb
212
213
  - lib/MrMurano/SyncUpDown.rb
213
214
  - lib/MrMurano/commands.rb
214
215
  - lib/MrMurano/commands/account.rb
@@ -221,12 +222,14 @@ files:
221
222
  - lib/MrMurano/commands/exportImport.rb
222
223
  - lib/MrMurano/commands/keystore.rb
223
224
  - lib/MrMurano/commands/logs.rb
225
+ - lib/MrMurano/commands/product.rb
224
226
  - lib/MrMurano/commands/productCreate.rb
225
227
  - lib/MrMurano/commands/productDelete.rb
226
228
  - lib/MrMurano/commands/productList.rb
227
229
  - lib/MrMurano/commands/productSpec.rb
228
230
  - lib/MrMurano/commands/productWrite.rb
229
231
  - lib/MrMurano/commands/serialNumberCmds.rb
232
+ - lib/MrMurano/commands/solution.rb
230
233
  - lib/MrMurano/commands/solutionCreate.rb
231
234
  - lib/MrMurano/commands/solutionDelete.rb
232
235
  - lib/MrMurano/commands/solutionList.rb