MrMurano 1.7.4 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
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