MrMurano 1.3.2 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,113 @@
1
+
2
+ command 'content list' do |c|
3
+ c.syntax = %{mr content list}
4
+ c.summary = %{List downloadable content for a product}
5
+ c.description = %{List downloadable content for a product
6
+
7
+ Data uploaded to a product's content area can be downloaded by devices using the
8
+ HTTP Device API. (http://docs.exosite.com/http/#list-available-content)
9
+ }
10
+ c.action do |args, options|
11
+ prd = MrMurano::ProductContent.new
12
+ prd.list.each{|item| say item}
13
+ end
14
+ end
15
+ alias_command :content, 'content list'
16
+
17
+ command 'content info' do |c|
18
+ c.syntax = %{mr content info <content id>}
19
+ c.summary = %{Show more info for a content item}
20
+ c.description = %{Show more info for a content item
21
+
22
+ Data uploaded to a product's content area can be downloaded by devices using the
23
+ HTTP Device API. (http://docs.exosite.com/http/#list-available-content)
24
+ }
25
+ c.action do |args, options|
26
+ if args[0].nil? then
27
+ say_error "Missing <content id>"
28
+ else
29
+ prd = MrMurano::ProductContent.new
30
+ prd.info(args[0]).each{|line| say "#{args[0]} #{line.join(' ')}"}
31
+ end
32
+ end
33
+ end
34
+
35
+ command 'content delete' do |c|
36
+ c.syntax = %{mr content delete <content id>}
37
+ c.summary = %{Delete a content item}
38
+ c.description = %{Delete a content item
39
+
40
+ Data uploaded to a product's content area can be downloaded by devices using the
41
+ HTTP Device API. (http://docs.exosite.com/http/#list-available-content)
42
+ }
43
+ c.action do |args, options|
44
+ if args[0].nil? then
45
+ say_error "Missing <content id>"
46
+ else
47
+ prd = MrMurano::ProductContent.new
48
+ pp prd.remove(args[0])
49
+ end
50
+ end
51
+ end
52
+
53
+ command 'content upload' do |c|
54
+ c.syntax = %{mr content upload <content id> <file>}
55
+ c.summary = %{Upload content}
56
+ c.description = %{Upload a content item
57
+
58
+ Data uploaded to a product's content area can be downloaded by devices using the
59
+ HTTP Device API. (http://docs.exosite.com/http/#list-available-content)
60
+ }
61
+ c.option '--meta STRING', %{Add extra meta info to the content item}
62
+
63
+ c.action do |args, options|
64
+ options.defaults :meta=>' '
65
+
66
+ if args[0].nil? then
67
+ say_error "Missing <content id>"
68
+ elsif args[1].nil? then
69
+ say_error "Missing <file>"
70
+ else
71
+ prd = MrMurano::ProductContent.new
72
+
73
+ ret = prd.info(args[0])
74
+ if ret.nil? then
75
+ pp prd.create(args[0], options.meta)
76
+ end
77
+
78
+ pp prd.upload(args[0], args[1])
79
+ end
80
+ end
81
+ end
82
+
83
+ command 'content download' do |c|
84
+ c.syntax = %{mr content download <content id>}
85
+ c.summary = %{Download a content item}
86
+ c.description = %{Download a content item
87
+
88
+ Data uploaded to a product's content area can be downloaded by devices using the
89
+ HTTP Device API. (http://docs.exosite.com/http/#list-available-content)
90
+ }
91
+ c.option '-o','--output FILE',%{save to this file}
92
+ c.action do |args, options|
93
+ if args[0].nil? then
94
+ say_error "Missing <content id>"
95
+ else
96
+ prd = MrMurano::ProductContent.new
97
+
98
+ if options.output.nil? then
99
+ prd.download(args[0]) # to stdout
100
+ else
101
+ outFile = Pathname.new(options.output)
102
+ outFile.open('w') do |io|
103
+ prd.download(args[0]) do |chunk|
104
+ io << chunk
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+
113
+ # vim: set ai et sw=2 ts=2 :
@@ -0,0 +1,123 @@
1
+ require 'pathname'
2
+ require 'json'
3
+ require 'fileutils'
4
+
5
+ command 'config export' do |c|
6
+ c.syntax = %{mr config export}
7
+ c.summary = %{Export data to a Solutionfile.json and .Solutionfile.secret}
8
+
9
+ c.option '--force', "Overwrite existing files."
10
+
11
+ c.action do |args, options|
12
+ solfile = Pathname.new($cfg['location.base'] + 'Solutionfile.json')
13
+ solsecret = Pathname.new($cfg['location.base'] + '.Solutionfile.secret')
14
+
15
+ if not options.force and (solfile.exist? or solsecret.exist?) then
16
+ say_error "Solutionfile.json or .Solutionfile.secret already exist."
17
+ say "Use --force to overwrite"
18
+ end
19
+
20
+ epf = 'routes.lua'
21
+ File.open(epf, 'w') do |dst|
22
+ MrMurano::Endpoint.new.locallist.each do |ed|
23
+ ed[:local_path].open do |fin|
24
+ FileUtils.copy_stream(fin, dst)
25
+ end
26
+ dst << "\n\n"
27
+ end
28
+ end
29
+
30
+ solf = {
31
+ :routes => 'epf',
32
+ :assets => $cfg['location.files'],
33
+ :default_page => $cfg['files.default_page'],
34
+ :services => {},
35
+ :modules => {}
36
+ }
37
+
38
+ dpwd = Pathname.new(Dir.pwd)
39
+ MrMurano::EventHandler.new.locallist.each do |ev|
40
+ rp = ev[:local_path].relative_path_from(dpwd).to_s
41
+ if solf[:services].has_key?(ev[:service]) then
42
+ solf[:services][ev[:service]][ev[:event]] = rp
43
+ else
44
+ solf[:services][ev[:service]] = {ev[:event] => rp}
45
+ end
46
+ end
47
+
48
+ MrMurano::Library.new.locallist.each do |lb|
49
+ solf[:modules][lb[:name]] = lb[:local_path].relative_path_from(dpwd).to_s
50
+ end
51
+
52
+ solfile.open('w') do |io|
53
+ io << JSON.pretty_generate(solf)
54
+ end
55
+
56
+ solsecret.open('w') do |io|
57
+ pff = Pathname.new(ENV['HOME']) + '.mrmurano/passwords'
58
+ pwd = MrMurano::Passwords.new(pff)
59
+ pwd.load
60
+ ps = pwd.get($cfg['net.host'], $cfg['user.name'])
61
+ io << {
62
+ :email => $cfg['user.name'],
63
+ :password => ps,
64
+ :solution_id => $cfg['solution.id'],
65
+ :product_id => $cfg['product.id']
66
+ }.to_json
67
+ end
68
+
69
+ end
70
+ end
71
+
72
+ command 'config import' do |c|
73
+ c.syntax = %{mr config import}
74
+ c.summary = %{Import data from a Solutionfile.json and .Solutionfile.secret}
75
+
76
+ c.action do |args, options|
77
+ solfile = ($cfg['location.base'] + 'Solutionfile.json')
78
+ solsecret = ($cfg['location.base'] + '.Solutionfile.secret')
79
+
80
+ if solfile.exist? then
81
+ # Is in JSON, which as a subset of YAML, so use YAML parser
82
+ solfile.open do |io|
83
+ sf = YAML.load(io)
84
+ $cfg.set('location.files', sf['assets']) if sf.has_key? 'assets'
85
+ $cfg.set('location.files', sf['file_dir']) if sf.has_key? 'file_dir'
86
+ $cfg.set('files.default_page', sf['default_page']) if sf.has_key? 'default_page'
87
+ end
88
+ end
89
+
90
+ if solsecret.exist? then
91
+ # Is in JSON, which as a subset of YAML, so use YAML parser
92
+ solsecret.open do |io|
93
+ ss = YAML.load(io)
94
+
95
+ pff = Pathname.new(ENV['HOME']) + '.mrmurano/passwords'
96
+ pwd = MrMurano::Passwords.new(pff)
97
+ pwd.load
98
+ ps = pwd.get($cfg['net.host'], ss['email'])
99
+ if ps.nil? then
100
+ pwd.set($cfg['net.host'], ss['email'], ss['password'])
101
+ pwd.save
102
+ elsif ps != ss['password'] then
103
+ y = ask("A different password for this account already exists. Overwrite? N/y")
104
+ if y =~ /^y/i then
105
+ pwd.set($cfg['net.host'], ss['email'], ss['password'])
106
+ pwd.save
107
+ end
108
+ else
109
+ # already set, nothing to do.
110
+ end
111
+
112
+ $cfg.set('solution.id', ss['solution_id']) if ss.has_key? 'solution_id'
113
+ $cfg.set('solution.id', ss['product_id']) if ss.has_key? 'product_id'
114
+ end
115
+ end
116
+
117
+ say "Configuration items have been imported."
118
+ say "Use `mr syncdown` get get all endpoints, modules, and event handlers"
119
+ end
120
+
121
+ end
122
+
123
+ # vim: set ai et sw=2 ts=2 :
data/lib/MrMurano/http.rb CHANGED
@@ -23,7 +23,10 @@ module MrMurano
23
23
  def curldebug(request)
24
24
  if $cfg['tool.curldebug'] then
25
25
  a = []
26
- a << %{curl -s -H 'Authorization: #{request['authorization']}'}
26
+ a << %{curl -s }
27
+ if request.key?('Authorization') then
28
+ a << %{-H 'Authorization: #{request['Authorization']}'}
29
+ end
27
30
  a << %{-H 'User-Agent: #{request['User-Agent']}'}
28
31
  a << %{-H 'Content-Type: #{request.content_type}'}
29
32
  a << %{-X #{request.method}}
@@ -42,19 +45,21 @@ module MrMurano
42
45
  end
43
46
  @http
44
47
  end
48
+ def http_reset
49
+ @http = nil
50
+ end
45
51
 
46
- def set_req_defaults(request)
52
+ def set_def_headers(request)
47
53
  request.content_type = 'application/json'
48
- request['authorization'] = 'token ' + token
54
+ request['Authorization'] = 'token ' + token
49
55
  request['User-Agent'] = "MrMurano/#{MrMurano::VERSION}"
50
56
  request
51
57
  end
52
58
 
53
59
  def workit(request, &block)
54
- set_req_defaults(request)
55
60
  curldebug(request)
56
61
  if block_given? then
57
- yield request, http()
62
+ return yield request, http()
58
63
  else
59
64
  response = http().request(request)
60
65
  case response
@@ -76,26 +81,37 @@ module MrMurano
76
81
 
77
82
  def get(path='', &block)
78
83
  uri = endPoint(path)
79
- workit(Net::HTTP::Get.new(uri), &block)
84
+ workit(set_def_headers(Net::HTTP::Get.new(uri)), &block)
80
85
  end
81
86
 
82
87
  def post(path='', body={}, &block)
83
88
  uri = endPoint(path)
84
89
  req = Net::HTTP::Post.new(uri)
90
+ set_def_headers(req)
85
91
  req.body = JSON.generate(body)
86
92
  workit(req, &block)
87
93
  end
88
94
 
95
+ def postf(path='', form={}, &block)
96
+ uri = endPoint(path)
97
+ req = Net::HTTP::Post.new(uri)
98
+ set_def_headers(req)
99
+ req.content_type = 'application/x-www-form-urlencoded; charset=utf-8'
100
+ req.form_data = form
101
+ workit(req, &block)
102
+ end
103
+
89
104
  def put(path='', body={}, &block)
90
105
  uri = endPoint(path)
91
106
  req = Net::HTTP::Put.new(uri)
107
+ set_def_headers(req)
92
108
  req.body = JSON.generate(body)
93
109
  workit(req, &block)
94
110
  end
95
111
 
96
112
  def delete(path='', &block)
97
113
  uri = endPoint(path)
98
- workit(Net::HTTP::Delete.new(uri), &block)
114
+ workit(set_def_headers(Net::HTTP::Delete.new(uri)), &block)
99
115
  end
100
116
 
101
117
  end
@@ -68,7 +68,7 @@ end
68
68
 
69
69
  command 'keystore set' do |c|
70
70
  c.syntax = %{mr keystore set <key> <value...>}
71
- c.description = %{Set teh value of a key in the Keystore}
71
+ c.description = %{Set the value of a key in the Keystore}
72
72
  c.action do |args,options|
73
73
  sol = MrMurano::Keystore.new
74
74
  sol.setkey(args[0], args[1..-1].join(' '))
@@ -0,0 +1,17 @@
1
+ require 'pp'
2
+
3
+ module MrMurano
4
+ module Verbose
5
+ def verbose(msg)
6
+ if $cfg['tool.verbose'] then
7
+ say msg
8
+ end
9
+ end
10
+ def debug(msg)
11
+ if $cfg['tool.debug'] then
12
+ say msg
13
+ end
14
+ end
15
+ end
16
+ end
17
+ # vim: set ai et sw=2 ts=2 :
@@ -1,4 +1,4 @@
1
1
  module MrMurano
2
- VERSION = '1.3.2'.freeze
2
+ VERSION = '1.4.0'.freeze
3
3
  end
4
4
 
data/lib/MrMurano.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'MrMurano/version'
2
+ require 'MrMurano/verbosing'
2
3
  require 'MrMurano/hash.rb'
3
4
  require 'MrMurano/http'
4
5
  require 'MrMurano/configFile'
@@ -9,8 +10,12 @@ require 'MrMurano/Solution-File.rb'
9
10
  require 'MrMurano/Solution-Services.rb'
10
11
  require 'MrMurano/Solution-Users.rb'
11
12
  require 'MrMurano/Solution-ServiceConfig.rb'
13
+ require 'MrMurano/Product'
12
14
 
15
+ require 'MrMurano/configCommand'
16
+ require 'MrMurano/contentCommand'
13
17
  require 'MrMurano/cors'
18
+ require 'MrMurano/exportImport'
14
19
  require 'MrMurano/keystore.rb'
15
20
  require 'MrMurano/logs.rb'
16
21
  require 'MrMurano/sync.rb'
@@ -0,0 +1,121 @@
1
+ require 'rubygems'
2
+ require 'commander/import'
3
+ require 'MrMurano'
4
+ require 'tempfile'
5
+
6
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
7
+ # Some kludgery to work around commander. Likely a better way to do this.
8
+ program :version, MrMurano::VERSION
9
+ program :description, %{Manage a Solution in Exosite's Murano}
10
+ command(:nop){ |c| c.action {|a,o|} }
11
+ default_command :nop
12
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
13
+
14
+ RSpec.describe MrMurano::Passwords, "#pwd" do
15
+ it "Creates a file " do
16
+ tmpfile = Dir.tmpdir + '/pwtest' # This way because Tempfile.new creates.
17
+ begin
18
+ pwd = MrMurano::Passwords.new( tmpfile )
19
+ pwd.save
20
+
21
+ expect( FileTest.exists?(tmpfile) )
22
+ ensure
23
+ File.unlink(tmpfile) if File.exists? tmpfile
24
+ end
25
+ end
26
+
27
+ it "Loads a file" do
28
+ Tempfile.open('test') do |tf|
29
+ tf << %{---
30
+ this.is.a.host:
31
+ user: password
32
+ }
33
+ tf.close
34
+
35
+ pwd = MrMurano::Passwords.new( tf.path )
36
+ pwd.load
37
+ ps = pwd.get('this.is.a.host', 'user')
38
+ expect(ps).to eq('password')
39
+ end
40
+ end
41
+
42
+ it "Saves a file" do
43
+ Tempfile.open('pstest') do |tf|
44
+ tf.close
45
+
46
+ pwd = MrMurano::Passwords.new(tf.path)
47
+ pwd.set('this.is.a.host', 'user3', 'passwords4')
48
+ pwd.save
49
+
50
+ File.open(tf.path) do |io|
51
+ data = io.read
52
+ expect(data).to eq(%{---
53
+ this.is.a.host:
54
+ user3: passwords4
55
+ })
56
+ end
57
+ end
58
+ end
59
+
60
+ it "Writes multiple hosts" do
61
+ Tempfile.open('pwtest') do |tf|
62
+ tf.close
63
+
64
+ pwd = MrMurano::Passwords.new(tf.path)
65
+ pwd.set('this.is.a.host', 'user3', 'passwords4')
66
+ pwd.save
67
+ pwd = nil
68
+
69
+ pwd = MrMurano::Passwords.new(tf.path)
70
+ pwd.load
71
+ ps = pwd.get('this.is.a.host', 'user3')
72
+ expect(ps).to eq('passwords4')
73
+ pwd = nil
74
+
75
+ pwd = MrMurano::Passwords.new(tf.path)
76
+ pwd.load
77
+ pwd.set('another.host', 'user9', 'passwords2')
78
+ pwd.save
79
+ pwd = nil
80
+
81
+ pwd = MrMurano::Passwords.new(tf.path)
82
+ pwd.load
83
+ ps = pwd.get('this.is.a.host', 'user3')
84
+ expect(ps).to eq('passwords4')
85
+ ps = pwd.get('another.host', 'user9')
86
+ expect(ps).to eq('passwords2')
87
+ pwd = nil
88
+
89
+ end
90
+ end
91
+
92
+ it "Write multiple users to same host" do
93
+ Tempfile.open('pwstest') do |tf|
94
+ tf.close
95
+
96
+ pwd = MrMurano::Passwords.new(tf.path)
97
+ pwd.set('this.is.a.host', 'user3', 'passwords4')
98
+ pwd.save
99
+ pwd = nil
100
+
101
+ pwd = MrMurano::Passwords.new(tf.path)
102
+ pwd.load
103
+ pwd.set('this.is.a.host', 'user9', 'passwords2')
104
+ pwd.save
105
+ pwd = nil
106
+
107
+ pwd = MrMurano::Passwords.new(tf.path)
108
+ pwd.load
109
+ ps = pwd.get('this.is.a.host', 'user3')
110
+ expect(ps).to eq('passwords4')
111
+ ps = pwd.get('this.is.a.host', 'user9')
112
+ expect(ps).to eq('passwords2')
113
+ pwd = nil
114
+
115
+ end
116
+ end
117
+
118
+
119
+ end
120
+
121
+ # vim: set ai et sw=2 ts=2 :
@@ -0,0 +1,112 @@
1
+ require 'MrMurano/version'
2
+ require 'MrMurano/verbosing'
3
+ require 'MrMurano/http'
4
+ require 'MrMurano/Product'
5
+ require 'MrMurano/configFile'
6
+
7
+ RSpec.describe MrMurano::ProductBase, "#product_base" do
8
+ before(:example) do
9
+ $cfg = MrMurano::Config.new
10
+ $cfg.load
11
+ $cfg['net.host'] = 'bizapi.hosted.exosite.io'
12
+ $cfg['product.id'] = 'XYZ'
13
+
14
+ @prd = MrMurano::ProductBase.new
15
+ allow(@prd).to receive(:token).and_return("TTTTTTTTTT")
16
+ end
17
+
18
+ it "initializes" do
19
+ uri = @prd.endPoint('/')
20
+ expect(uri.to_s).to eq("https://bizapi.hosted.exosite.io/api:1/product/XYZ/")
21
+ end
22
+
23
+ it "can get" do
24
+ stub_request(:get, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/").
25
+ with(:headers=>{'Authorization'=>'token TTTTTTTTTT',
26
+ 'Content-Type'=>'application/json'}).
27
+ to_return(body: "Fooo")
28
+
29
+ ret = @prd.get('/')
30
+ expect(ret).to eq("Fooo")
31
+ end
32
+
33
+ it "returns hash when getting empty body" do
34
+ stub_request(:get, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/").
35
+ with(:headers=>{'Authorization'=>'token TTTTTTTTTT',
36
+ 'Content-Type'=>'application/json'}).
37
+ to_return(body: "")
38
+
39
+ ret = @prd.get('/')
40
+ expect(ret).to eq({})
41
+ end
42
+
43
+ it "auto parses JSON responses" do
44
+ stub_request(:get, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/").
45
+ with(:headers=>{'Authorization'=>'token TTTTTTTTTT',
46
+ 'Content-Type'=>'application/json'}).
47
+ to_return(body: %{{"first": "str", "sec":[1,2,3], "third":{"a":"b"}}})
48
+
49
+ ret = @prd.get('/')
50
+ expect(ret).to eq({:sec=>[1,2,3],:third=>{:a=>'b'},:first=>'str'})
51
+ end
52
+
53
+ it "can post nothing" do
54
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/").
55
+ with(:headers=>{'Authorization'=>'token TTTTTTTTTT',
56
+ 'Content-Type'=>'application/json'}).
57
+ to_return(body: "")
58
+ ret = @prd.post('/')
59
+ expect(ret).to eq({})
60
+ end
61
+
62
+ it "can post json" do
63
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/").
64
+ with(headers: {'Authorization'=>'token TTTTTTTTTT',
65
+ 'Content-Type'=>'application/json'},
66
+ body: {:this=>"is", :a=>'test'}).
67
+ to_return(body: "")
68
+ ret = @prd.post('/', {:this=>"is", :a=>'test'})
69
+ expect(ret).to eq({})
70
+ end
71
+
72
+ it "can post form data" do
73
+ stub_request(:post, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/").
74
+ with(headers: {'Authorization'=>'token TTTTTTTTTT',
75
+ 'Content-Type'=>'application/x-www-form-urlencoded'},
76
+ body: "this=is&a=test").
77
+ to_return(body: "")
78
+ ret = @prd.postf('/', {:this=>"is", :a=>'test'})
79
+ expect(ret).to eq({})
80
+ end
81
+
82
+ it "can put nothing" do
83
+ stub_request(:put, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/").
84
+ with(:headers=>{'Authorization'=>'token TTTTTTTTTT',
85
+ 'Content-Type'=>'application/json'}).
86
+ to_return(body: "")
87
+ ret = @prd.put('/')
88
+ expect(ret).to eq({})
89
+ end
90
+
91
+ it "can put json" do
92
+ stub_request(:put, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/").
93
+ with(headers: {'Authorization'=>'token TTTTTTTTTT',
94
+ 'Content-Type'=>'application/json'},
95
+ body: {:this=>"is", :a=>'test'}).
96
+ to_return(body: "")
97
+ ret = @prd.put('/', {:this=>"is", :a=>'test'})
98
+ expect(ret).to eq({})
99
+ end
100
+
101
+ it "can delete" do
102
+ stub_request(:delete, "https://bizapi.hosted.exosite.io/api:1/product/XYZ/").
103
+ with(headers: {'Authorization'=>'token TTTTTTTTTT',
104
+ 'Content-Type'=>'application/json'}).
105
+ to_return(body: "")
106
+ ret = @prd.delete('/')
107
+ expect(ret).to eq({})
108
+ end
109
+
110
+ end
111
+
112
+ # vim: set ai et sw=2 ts=2 :