MuranoCLI 3.0.4 → 3.0.5

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: ec3cf38c4b880ec614977a7f6d95fc8b5ac69e68
4
- data.tar.gz: 41b0bf32389f080867f13df0906262881ebad5ed
3
+ metadata.gz: 73e2f9256da32058214bb3f721407d9fd480b951
4
+ data.tar.gz: 947a9c30a1632fae14e8c5e1560a52050bc0748b
5
5
  SHA512:
6
- metadata.gz: 9053361d890f83cf9037bfd811bac313b1893df69daf88a13b5dab2a744c01239c7f19fb49428b1949fb578a8d50ab11a61a3033b8d1dd65f202b706de0cd116
7
- data.tar.gz: af31c49ad0e42dc9a6e6eb4a6953b591b98b58c2547377e9b7e0e8d3ae53fad38256c1c72a699cdb8e18a6848ef489234cbf7408c8739cbc318e0a99f407a2c4
6
+ metadata.gz: 770f834abc53c64993f18fff880379b6ebb2ed24905fb662a856e66b103ac8d36cca955fa893fd9cca8a355cbbe39f08c38a71153450680fa1e8e8c5b493935f
7
+ data.tar.gz: 5f6ccacfaa8a60c09c8e551bbb17844266e5ddb7156f52b701b470c07faaff24fe5203c81b790580f8c3876f110667e4cef2530b815996a0ebda5027838ae247
@@ -7,10 +7,12 @@ report/
7
7
  .rspec_examples.txt
8
8
  Gemfile.lock
9
9
  .bundle/
10
+ tags
10
11
 
11
12
  # 2017-05-12: Testing!
12
13
  *.out
13
14
  .rake_build.out
15
+ curldebug.out
14
16
 
15
17
  rspec-*.html
16
18
  *.rspec.html
data/.trustme.sh CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/bin/bash
2
- # Last Modified: 2017.08.16
2
+ # Last Modified: 2017.09.20
3
3
  # vim:tw=0:ts=2:sw=2:et:norl:spell
4
4
 
5
5
  # WHAT: A Continuous Integration (CI) script for kicking the build
@@ -171,6 +171,19 @@ function rspec_it() {
171
171
  # business as you do when developing.
172
172
  #rspec_it
173
173
 
174
+ function ctags_it() {
175
+ annoucement "CTAGS IT"
176
+ ctags -R \
177
+ --exclude=coverage \
178
+ --exclude=docs \
179
+ --exclude=pkg \
180
+ --exclude=report \
181
+ --exclude=spec \
182
+ --verbose=yes
183
+ /bin/ls -la tags >> ${OUT_FILE}
184
+ }
185
+ ctags_it
186
+
174
187
  time_n=$(date +%s.%N)
175
188
  time_elapsed=$(echo "$time_n - $time_0" | bc -l)
176
189
  annoucement "DONE!"
data/.trustme.vim CHANGED
@@ -23,6 +23,8 @@
23
23
  " autocmd BufWritePost <buffer> silent !./.trustme.sh &
24
24
  "augroup END
25
25
 
26
+ autocmd BufRead *.rb set tags=/exo/clients/exosite/exosite-murcli/tags
27
+
26
28
  "echomsg 'Calling trustme.sh'
27
29
  silent !./.trustme.sh &
28
30
 
data/README.markdown CHANGED
@@ -1,10 +1,5 @@
1
1
  # Murano Command Line Interface (CLI)
2
2
 
3
- [![Gem
4
- Version](https://badge.fury.io/rb/MuranoCLI.svg)](https://badge.fury.io/rb/MuranoCLI)
5
- [![Build Status](https://travis-ci.org/exosite/MuranoCLI.svg?branch=master)](https://travis-ci.org/exosite/MuranoCLI)
6
- [![Inline docs](http://inch-ci.org/github/exosite/MuranoCLI.svg?branch=master)](http://inch-ci.org/github/exosite/MuranoCLI)
7
-
8
3
  Do more from the command line with [Murano](https://exosite.com/platform/).
9
4
 
10
5
  MuranoCLI interacts with Murano and makes different tasks easier.
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.08.22 /coding: utf-8
1
+ # Last Modified: 2017.09.21 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -55,6 +55,7 @@ Or set your password with `murano password set <username>`.
55
55
 
56
56
  def login_info
57
57
  warned_once = false
58
+
58
59
  if user.empty?
59
60
  prologue = 'No Murano user account found.'
60
61
  unless $cfg.prompt_if_logged_off
@@ -70,9 +71,8 @@ Or set your password with `murano password set <username>`.
70
71
  $project.refresh_user_name
71
72
  MrMurano::Verbose.whirly_unpause
72
73
  end
73
- pwd_path = $cfg.file_at('passwords', :user)
74
- pwd_file = MrMurano::Passwords.new(pwd_path)
75
- pwd_file.load
74
+
75
+ pwd_file = pwd_file_load
76
76
  user_pass = pwd_file.get(host, user)
77
77
  if user_pass.nil?
78
78
  prologue = "No Murano password found for #{user}."
@@ -85,19 +85,42 @@ Or set your password with `murano password set <username>`.
85
85
  error(%(#{prologue} #{LOGIN_NOTICE}).strip) unless warned_once
86
86
  user_pass = ask('Password: ') { |q| q.echo = '*' }
87
87
  pwd_file.set(host, user, user_pass)
88
+ pwd_file.set(host, user + '/twofactor', nil)
88
89
  pwd_file.save
89
90
  MrMurano::Verbose.whirly_unpause
91
+ else
92
+ @twofactor_token = token_twofactor_lookup(pwd_file)
90
93
  end
91
- creds = {
94
+
95
+ {
92
96
  email: user,
93
97
  password: user_pass,
94
98
  }
95
- creds
99
+ end
100
+
101
+ def pwd_file_load
102
+ pwd_path = $cfg.file_at('passwords', :user)
103
+ pwd_file = MrMurano::Passwords.new(pwd_path)
104
+ pwd_file.load
105
+ pwd_file
106
+ end
107
+
108
+ def token_twofactor_lookup(pwd_file)
109
+ twoftoken = pwd_file.lookup(host, user + '/twofactor')
110
+ if twoftoken.to_s.empty?
111
+ nil
112
+ elsif twoftoken !~ /^[a-fA-F0-9]+$/
113
+ warning "Malformed twofactor token: #{twoftoken}"
114
+ nil
115
+ else
116
+ twoftoken
117
+ end
96
118
  end
97
119
 
98
120
  # ---------------------------------------------------------------------
99
121
 
100
122
  def token
123
+ return '' if defined?(@logging_on) && @logging_on
101
124
  token_fetch if @token.to_s.empty?
102
125
  @token
103
126
  end
@@ -107,32 +130,139 @@ Or set your password with `murano password set <username>`.
107
130
  end
108
131
 
109
132
  def token_fetch
110
- # Cannot have token call token, so cannot use Http::workit.
111
- uri = endpoint('token/')
112
- request = Net::HTTP::Post.new(uri)
113
- request['User-Agent'] = "MrMurano/#{MrMurano::VERSION}"
114
- request.content_type = 'application/json'
115
- curldebug(request)
116
- #request.basic_auth(username(), password())
117
- request.body = JSON.generate(login_info)
133
+ @logging_on = true
134
+ creds = login_info
135
+ @token = nil
136
+
137
+ # If 2fa token found, verify it works.
138
+ unless @twofactor_token.nil?
139
+ get('token/' + @twofactor_token) do |request, http|
140
+ http.request(request) do |response|
141
+ if response.is_a?(Net::HTTPSuccess)
142
+ # response.body is, e.g., "{\"email\":\"xxx@yyy.zzz\",\"ttl\":172800}"
143
+ @token = @twofactor_token
144
+ end
145
+ end
146
+ end
147
+ unless @token.nil?
148
+ @logging_on = false
149
+ return
150
+ end
151
+ @twofactor_token = nil
152
+ end
118
153
 
119
154
  MrMurano::Verbose.whirly_start('Logging in...')
120
- response = http.request(request)
155
+ post('token/', creds) do |request, http|
156
+ http.request(request) do |response|
157
+ reply = JSON.parse(response.body, json_opts)
158
+ if response.is_a?(Net::HTTPSuccess)
159
+ @token = reply[:token]
160
+ elsif response.is_a?(Net::HTTPConflict) && reply[:message] == 'twofactor'
161
+ MrMurano::Verbose.whirly_interject do
162
+ # Prompt user for emailed code.
163
+ token_twofactor_fetch(creds)
164
+ end
165
+ else
166
+ showHttpError(request, response)
167
+ error 'Check to see if username and password are correct.'
168
+ unless ENV['MURANO_PASSWORD'].to_s.empty?
169
+ pwd_path = $cfg.file_at('passwords', :user)
170
+ warning "NOTE: MURANO_PASSWORD specifies the password; it was not read from #{pwd_path}"
171
+ end
172
+ end
173
+ end
174
+ end
121
175
  MrMurano::Verbose.whirly_stop
176
+ @logging_on = false
177
+ end
122
178
 
123
- case response
124
- when Net::HTTPSuccess
125
- token = JSON.parse(response.body, json_opts)
126
- @token = token[:token]
127
- else
128
- showHttpError(request, response)
129
- error 'Check to see if username and password are correct.'
130
- unless ENV['MURANO_PASSWORD'].to_s.empty?
131
- pwd_path = $cfg.file_at('passwords', :user)
132
- warning "NOTE: MURANO_PASSWORD specifies the password; it was not read from #{pwd_path}"
133
- end
134
- @token = nil
179
+ def token_twofactor_fetch(creds)
180
+ error 'Two-factor Authentication'
181
+ warning 'A verification code has been sent to your email.'
182
+ code = ask('Please enter the code here to continue: ').strip
183
+ unless code =~ /^[a-fA-F0-9]+$/
184
+ error 'Expected token to contain only numbers and hexadecimal letters.'
185
+ exit 1
186
+ end
187
+ MrMurano::Verbose.whirly_start('Verifying code...')
188
+
189
+ path = 'key/' + code
190
+
191
+ response = get(path)
192
+ # Response is, e.g., {
193
+ # purpose: "twofactor",
194
+ # status: "exists",
195
+ # email: "xxx@yyy.zzz",
196
+ # bizid: null,
197
+ # businessName: null, }
198
+ return if response.nil?
199
+
200
+ response = post(path, password: creds[:password])
201
+ # Response is, e.g., { "token": "..." }
202
+ return if response.nil?
203
+
204
+ @twofactor_token = response[:token]
205
+ pwd_file = pwd_file_load
206
+ pwd_file.set(host, user + '/twofactor', @twofactor_token)
207
+ pwd_file.save
208
+ @token = @twofactor_token
209
+ MrMurano::Verbose.whirly_stop
210
+
211
+ warning 'Please run `murano logout --token` to clear your two-factor token when finished.'
212
+ end
213
+
214
+ def logout(token_delete_only)
215
+ @logging_on = true
216
+
217
+ pwd_file = pwd_file_load
218
+ twoftoken = token_twofactor_lookup(pwd_file)
219
+
220
+ # First, delete/invalidate the remote token.
221
+ unless twoftoken.to_s.empty?
222
+ @suppress_error = true
223
+ delete('token/' + twoftoken)
224
+ # The response is nil if the token was not recognized, otherwise it's
225
+ # {}. We don't really care, since we're going to forget our copy of
226
+ # the token, anyway.
227
+ @suppress_error = false
228
+ end
229
+
230
+ net_host = verify_set('net.host')
231
+ user_name = verify_set('user.name')
232
+ if net_host && user_name
233
+ pwd_file = MrMurano::Passwords.new
234
+ pwd_file.load
235
+ pwd_file.remove(net_host, user_name) unless token_delete_only
236
+ pwd_file.remove(net_host, user_name + '/twofactor')
237
+ pwd_file.save
238
+ end
239
+
240
+ clear_from_config(net_host, user_name) unless token_delete_only
241
+
242
+ @logging_on = false
243
+ end
244
+
245
+ def clear_from_config(net_host, user_name)
246
+ user_net_host = $cfg.get('net.host', :user)
247
+ user_net_host = $cfg.get('net.host', :defaults) if user_net_host.nil?
248
+ user_user_name = $cfg.get('user.name', :user)
249
+ # Only clear user name from the user config if the net.host
250
+ # or user.name did not come from a different config, like the
251
+ # --project config.
252
+ return unless (user_net_host == net_host) && (user_user_name == user_name)
253
+ $cfg.set('user.name', nil, :user)
254
+ $cfg.set('business.id', nil, :user)
255
+ $cfg.set('business.name', nil, :user)
256
+ end
257
+
258
+ def verify_set(cfg_key)
259
+ cfg_val = $cfg.get(cfg_key)
260
+ if cfg_val.to_s.empty?
261
+ cfg_val = nil
262
+ cfg_key_q = MrMurano::Verbose.fancy_ticks(cfg_key)
263
+ MrMurano::Verbose.warning("No config key #{cfg_key_q}: no password to delete")
135
264
  end
265
+ cfg_val
136
266
  end
137
267
 
138
268
  # ---------------------------------------------------------------------
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.09.11 /coding: utf-8
1
+ # Last Modified: 2017.09.20 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -109,12 +109,7 @@ module MrMurano
109
109
 
110
110
  def self.missing_business_id_msg
111
111
  %(
112
- Missing Business ID.
113
- Call `#{MrMurano::EXE_NAME} business list` to get a list of business IDs.
114
- Set the ID temporarily using --config business.id=<ID>
115
- or add to the project config using \`#{MrMurano::EXE_NAME} config business.id <ID>\`
116
- or add to the user config using \`#{MrMurano::EXE_NAME} config business.id <ID> --user\`
117
- or set it interactively using \`#{MrMurano::EXE_NAME} init\`
112
+ business ID not specified. For hints: #{MrMurano::EXE_NAME} business --help
118
113
  ).strip
119
114
  end
120
115
 
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.08.31 /coding: utf-8
1
+ # Last Modified: 2017.09.20 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -301,7 +301,7 @@ module MrMurano
301
301
  if the_cmd.name != 'help' && !the_cmd.project_not_required && !@project_exists
302
302
  error %(The "#{the_cmd.name}" command only works in a Murano project.)
303
303
  say INVALID_PROJECT_HINT
304
- # Note that commnander-rb uses an at_exit hook, which we hack around.
304
+ # Note that commander-rb uses an at_exit hook, which we hack around.
305
305
  @runner.command_exit = 1
306
306
  false
307
307
  end
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.08.31 /coding: utf-8
1
+ # Last Modified: 2017.09.27 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -9,37 +9,44 @@ module MrMurano
9
9
  class ExchangeElement
10
10
  include HashInit
11
11
 
12
- # The meta is the element hash returned from the platform,
13
- # i.e., all of the other attrs in a Hash.
14
- attr_reader :meta
12
+ # *** BizAPI members.
15
13
 
16
- # The Exchange Element's Business ID context.
17
- attr_accessor :bizid
14
+ # These members are a copy of and ordered according to
15
+ # (the moving target known as) BizAPI. See:
16
+ #
17
+ # <bizapi>/lib/api/route/exchange/schemas/element.js::elementWithoutId
18
+ #
19
+ # But really what's returned is what's in the Mongo store, ha!
18
20
 
19
21
  # The Exchange Element's Element ID.
22
+ # [lb] tempted to say :element_id, but keep it matchy, in spite of #linter.
20
23
  attr_accessor :elementId
21
- #attr_accessor :element_id
22
24
 
23
- # The Purchase ID is nil unless the user has added/purchased this element.
24
- attr_accessor :purchaseId
25
- #attr_accessor :purchase_id
26
-
27
- # The <bizId>/exchange/ endpoint returns a list of flat dictionaries.
28
- # The <bizId>/purchase/ endpoint, on the other hand, puts the remaining
29
- # items in an object under an "element" key.
25
+ # The Exchange Element's Business ID context.
26
+ attr_accessor :bizid
30
27
 
31
- # The type is one of: download
28
+ # The type is one of: download | product | application | contactSales
29
+ # though BizAPI 'list' command also accepts: service
32
30
  attr_accessor :type
33
- #attr_accessor :action_type
34
31
 
35
32
  # The friendly, descriptive name of the Exchange Element.
36
33
  attr_accessor :name
37
34
 
38
- # FIXME/EXPLAIN: Is this what the Lua code calls the service?
39
- attr_accessor :apiServiceName
40
-
41
35
  # The image associated with the Exchange Element; not used by the CLI.
42
36
  attr_accessor :image
37
+ # Contains ancestors:
38
+ # :thumbnail
39
+ # :url
40
+ # :filename
41
+ # :color
42
+ # :type
43
+ # :size
44
+ # :detail
45
+ # :url
46
+ # :filename
47
+ # :color
48
+ # :type
49
+ # :size
43
50
 
44
51
  # The short description describing the Exchange Element.
45
52
  attr_accessor :description
@@ -47,19 +54,75 @@ module MrMurano
47
54
  # The long description describing the Exchange Element.
48
55
  attr_accessor :markdown
49
56
 
50
- # The Murano business tiers to which this element applies.
51
- # One or more of: ["free", "developer", "professional", "enterprise"]
52
- attr_accessor :tiers
57
+ # The source associated with the Exchange Element; not currently used.
58
+ attr_accessor :source
59
+ # Contains ancestors:
60
+ # :from
61
+ # - One of: service | github | attachment | url
62
+ # :name
63
+ # :url
64
+ # :token
53
65
 
54
- # Tags used to describe the Exchange Element.
66
+ # Array of tags used to describe the Exchange Element.
55
67
  attr_accessor :tags
56
68
 
69
+ # The attachment associated with 'attachment' sources; not currently used.
70
+ attr_accessor :attachment
71
+ # Contains ancestors:
72
+ # :download
73
+ # :url
74
+ # :filename
75
+ # :type
76
+ # :size
77
+
78
+ # The contact the user wrote for with the Exchange Element; not currently used.
79
+ attr_accessor :contact
80
+
81
+ # The specs the user wrote for the Exchange Element; not currently used.
82
+ attr_accessor :specs
83
+
84
+ # The active value is boolean; not currently used.
85
+ attr_accessor :active
86
+
87
+ # The access associated with the Exchange Element; not currently used.
88
+ # One of: public | private | network.
89
+ attr_accessor :access
90
+
91
+ # "approval" is not really documented. Code shows values: pending | approved.
92
+ attr_accessor :approval
93
+
94
+ # *** Values returned from BizAPI but defined specially.
95
+
96
+ # The Purchase ID is nil unless the user has added/purchased this element.
97
+ attr_accessor :purchaseId
98
+ #attr_accessor :purchase_id
99
+
100
+ # The <bizId>/exchange/ endpoint returns a list of flat dictionaries.
101
+ # The <bizId>/purchase/ endpoint, on the other hand, puts the remaining
102
+ # items in an object under an "element" key.
103
+
104
+ # FIXME/EXPLAIN: Is this what the Lua code calls the service?
105
+ attr_accessor :apiServiceName
106
+
107
+ # The Murano business tiers to which this element applies.
108
+ # Zero or more of: ["free", "developer", "professional", "enterprise"]
109
+ # NOTE: The 'tiers' values appears to be returned directly from Mongo DB.
110
+ # Specifically, BizAPI sets tiers = [] on new elements, but for global
111
+ # elements (in bootstrap/db/element.json), tiers is non-empty. [lb]
112
+ attr_accessor :tiers
113
+
57
114
  # Actions associated with the Exchange Element.
58
115
  attr_accessor :actions
59
116
  # The actions is an array with one dict element with:
60
117
  # 'url', 'type' (e.g., 'download), and 'primary' (bool).
61
118
 
62
- # MurCLI-only: Based on purchaseId and tiers, state of Element in Business.
119
+ # *** Internal Murano CLI variables (i.e., not in BizAPI).
120
+
121
+ # The meta is the Hash of the Exchange Element returned from the platform.
122
+ attr_reader :meta
123
+
124
+ # Based on purchaseId and tiers, state of Element in Business. One of:
125
+ # :available | :upgrade | :added
63
126
  attr_accessor :statusable
64
127
 
65
128
  ELEM_KEY_TRANSLATE = {
@@ -68,6 +131,7 @@ module MrMurano
68
131
  }.freeze
69
132
 
70
133
  def initialize(*hash)
134
+ @show_errors = $cfg['tool.verbose']
71
135
  hash = [hash] unless hash.is_a? Array
72
136
  camel_cased = {}
73
137
  hash.first.each do |key, val|
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.08.31 /coding: utf-8
1
+ # Last Modified: 2017.09.27 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -24,7 +24,21 @@ module MrMurano
24
24
  end
25
25
 
26
26
  def fetch_type(part)
27
- whirly_start('Fetching Elements...')
27
+ # [lb] not super happy about mixing presentation with other logic
28
+ # but this is quick and dirty.
29
+ case part
30
+ when '/element/'
31
+ qualifier = 'All'
32
+ when '/purchase/'
33
+ qualifier = 'Purchased'
34
+ else
35
+ raise 'Unexpected error'
36
+ end
37
+ whirly_start("Fetching #{qualifier} Elements...")
38
+
39
+ # FIXME/2017-09-27: Support Pagination. BizAPI accepts four settings:
40
+ # type, offset, limit, and select.
41
+ # <bizapi>/lib/api/route/exchange/schemas/element.js
28
42
  ret = get('exchange/' + bid + part) do |request, http|
29
43
  response = http.request(request)
30
44
  case response
@@ -50,14 +64,23 @@ module MrMurano
50
64
  lookp = {}
51
65
  # Get the user's Business metadata, including their Business tier.
52
66
  overview if @ometa.nil?
67
+ elems = fetch_elements(lookp)
68
+ fetch_purchased(lookp)
69
+ prepare_elements(elems, **opts)
70
+ end
71
+
72
+ def fetch_elements(lookp)
53
73
  # Fetch the list of Elements, including Added, Available, and Upgradeable.
54
74
  items = fetch_type('/element/')
55
75
  # Prepare a lookup of the Elements.
56
- elems = items.map do |meta|
76
+ items.map do |meta|
57
77
  elem = MrMurano::ExchangeElement.new(meta)
58
78
  lookp[elem.elementId] = elem
59
79
  elem
60
80
  end
81
+ end
82
+
83
+ def fetch_purchased(lookp)
61
84
  # Fetch the list of Purchased elements.
62
85
  items = fetch_type('/purchase/')
63
86
  # Update the list of all Elements to indicate which have been purchased.
@@ -67,8 +90,8 @@ module MrMurano
67
90
  elem.purchaseId = meta[:purchaseId]
68
91
  # Sanity check.
69
92
  meta[:element].each do |key, val|
70
- next if elem.send(key) == val
71
- warning(
93
+ next if verify_purchase_vs_element(elem, key, val)
94
+ verbose(
72
95
  'Unexpected: Exchange Purchase element meta differs: ' \
73
96
  "key: #{key} / elem: #{elem.send(key)} / purchase: #{val}"
74
97
  )
@@ -77,7 +100,13 @@ module MrMurano
77
100
  warning("Unexpected: No Element found for Exchange Purchase: elementId: #{meta[:elementId]}")
78
101
  end
79
102
  end
80
- prepare_elements(elems, **opts)
103
+ end
104
+
105
+ def verify_purchase_vs_element(elem, key, val)
106
+ elem.send(key) == val
107
+ rescue NoMethodError
108
+ verbose("Unexpected: Exchange Element missing key found in Purchase Element: #{key}")
109
+ true
81
110
  end
82
111
 
83
112
  def prepare_elements(elems, filter_id: nil, filter_name: nil, filter_fuzzy: nil)
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.08.23 /coding: utf-8
1
+ # Last Modified: 2017.09.20 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -69,6 +69,10 @@ module MrMurano
69
69
  ).strip
70
70
  return ENV['MR_PASSWORD']
71
71
  end
72
+ lookup(host, user)
73
+ end
74
+
75
+ def lookup(host, user)
72
76
  return nil unless @data.is_a?(Hash)
73
77
  return nil unless @data.key?(host)
74
78
  return nil unless @data[host].is_a?(Hash)
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.09.12 /coding: utf-8
1
+ # Last Modified: 2017.09.21 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -665,6 +665,8 @@ module MrMurano
665
665
  # @param options [Hash, Commander::Command::Options] Options on operation
666
666
  # @param selected [Array<String>] Filters for _matcher
667
667
  def syncup(options={}, selected=[])
668
+ return 0 unless api_id?
669
+
668
670
  options = elevate_hash(options)
669
671
  options[:asdown] = false
670
672
 
@@ -727,6 +729,8 @@ module MrMurano
727
729
  # @param options [Hash, Commander::Command::Options] Options on operation
728
730
  # @param selected [Array<String>] Filters for _matcher
729
731
  def syncdown(options={}, selected=[])
732
+ return 0 unless api_id?
733
+
730
734
  options = elevate_hash(options)
731
735
  options[:asdown] = true
732
736
  options[:skip_missing_warning] = true
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.09.11 /coding: utf-8
1
+ # Last Modified: 2017.09.20 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -20,6 +20,16 @@ command :business do |c|
20
20
  c.summary = %(About business)
21
21
  c.description = %(
22
22
  Commands for working with businesses.
23
+
24
+ If you need to set the business ID, try some of the following:
25
+
26
+ - Get a list of Business IDs: #{MrMurano::EXE_NAME} business list
27
+
28
+ - Specify the ID explictly: #{MrMurano::EXE_NAME} <cmd> --config business.id=<ID>
29
+ Add the ID to a project config: #{MrMurano::EXE_NAME} config business.id <ID>
30
+ Add the ID to the user config: #{MrMurano::EXE_NAME} config business.id <ID> --user
31
+ Setup a project interactively: #{MrMurano::EXE_NAME} init
32
+
23
33
  ).strip
24
34
  c.project_not_required = true
25
35
  c.subcmdgrouphelp = true
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.08.16 /coding: utf-8
1
+ # Last Modified: 2017.09.13 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -19,11 +19,38 @@ Set the CORS with `murano cors set`.
19
19
  ).strip
20
20
  c.project_not_required = true
21
21
 
22
+ c.example %(
23
+ Output CORS parameters in an ASCII table.
24
+ ).strip, 'murano cors'
25
+
26
+ c.example %(
27
+ Output CORS parameters as JSON.
28
+ ).strip, 'murano cors --json'
29
+
30
+ c.example %(
31
+ Output CORS parameters in Yaml.
32
+ ).strip, 'murano cors --yaml'
33
+
34
+ c.example %(
35
+ Output CORS parameters as comma-separated values.
36
+ ).strip, 'murano cors --csv'
37
+
38
+ c.example %(
39
+ Output CORS parameters pretty-printed as a Ruby Hash.
40
+ ).strip, 'murano cors --pp'
41
+
22
42
  c.action do |args, _options|
23
43
  c.verify_arg_count!(args)
24
44
  sol = MrMurano::Webservice::Cors.new
25
45
  ret = sol.fetch
26
- sol.outf ret
46
+ sol.outf(ret) do |obj, ios|
47
+ # Called if tool.outformat is 'best' or 'csv' (not 'json', 'yaml', or 'pp').
48
+ headers = obj.keys.sort
49
+ row = []
50
+ headers.each { |key| row << obj[key] }
51
+ rows = [row]
52
+ sol.tabularize({ headers: headers, rows: rows }, ios)
53
+ end
27
54
  end
28
55
  end
29
56
 
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.08.31 /coding: utf-8
1
+ # Last Modified: 2017.09.20 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -62,6 +62,7 @@ Element status:
62
62
  cmd_defaults_id_and_name(options)
63
63
 
64
64
  xchg = MrMurano::Exchange.new
65
+ xchg.must_business_id!
65
66
 
66
67
  elems, available, purchased = find_elements(xchg, options, args[0])
67
68
  if options.added.nil?
@@ -169,7 +170,7 @@ def cmd_exchange_header_and_elems(elems, options)
169
170
  # Calculate the width of each column except the last (:description).
170
171
  headers[0..-2].each do |key|
171
172
  elem_with_max = elems.max { |a, b| a.send(key).length <=> b.send(key).length }
172
- width_taken += elem_with_max.send(key).length
173
+ width_taken += elem_with_max.send(key).length unless elem_with_max.nil?
173
174
  width_taken += ' | '.length
174
175
  end
175
176
  width_taken += ' | '.length
@@ -233,6 +234,7 @@ Add an Exchange Element to your Business.
233
234
  cmd_defaults_id_and_name(options)
234
235
 
235
236
  xchg = MrMurano::Exchange.new
237
+ xchg.must_business_id!
236
238
 
237
239
  # If the user specifies filter_id, we could try to fetch that Element
238
240
  # directly (e.g., by calling exchange/<bizId>/element/<elemId>),
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.08.23 /coding: utf-8
1
+ # Last Modified: 2017.09.21 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -42,43 +42,16 @@ it will remove that user's password from the password file.
42
42
  Essentially, this command is the same as:
43
43
 
44
44
  murano password delete <username>
45
+ murano password delete <username>/twofactor
45
46
  murano config --unset --user user.name
46
47
  ).strip
47
48
  c.project_not_required = true
48
49
 
49
- c.action do |args, _options|
50
- c.verify_arg_count!(args)
51
-
52
- net_host = verify_set('net.host')
53
- user_name = verify_set('user.name')
54
- if net_host && user_name
55
- psd = MrMurano::Passwords.new
56
- psd.load
57
- psd.remove(net_host, user_name)
58
- psd.save
59
- end
60
-
61
- user_net_host = $cfg.get('net.host', :user)
62
- user_net_host = $cfg.get('net.host', :defaults) if user_net_host.nil?
63
- user_user_name = $cfg.get('user.name', :user)
64
- if (user_net_host == net_host) && (user_user_name == user_name)
65
- # Only clear user name from the user config if the net.host
66
- # or user.name did not come from a different config, like the
67
- # --project config.
68
- $cfg.set('user.name', nil, :user)
69
- $cfg.set('business.id', nil, :user)
70
- $cfg.set('business.name', nil, :user)
71
- end
72
- end
50
+ c.option '--token', 'Remove just the two-factor token'
73
51
 
74
- def verify_set(cfg_key)
75
- cfg_val = $cfg.get(cfg_key)
76
- if cfg_val.to_s.empty?
77
- cfg_val = nil
78
- cfg_key_q = MrMurano::Verbose.fancy_ticks(cfg_key)
79
- MrMurano::Verbose.warning("No config key #{cfg_key_q}: no password to delete")
80
- end
81
- cfg_val
52
+ c.action do |args, options|
53
+ c.verify_arg_count!(args)
54
+ MrMurano::Account.instance.logout(options.token)
82
55
  end
83
56
  end
84
57
 
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.08.16 /coding: utf-8
1
+ # Last Modified: 2017.09.20 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -10,15 +10,16 @@ require 'MrMurano/Solution'
10
10
 
11
11
  command :usage do |c|
12
12
  c.syntax = %(murano usage)
13
- c.summary = %(Get usage info for solution(s))
13
+ c.summary = %(Get usage info for the Application and Product)
14
14
  c.description = %(
15
- Get usage info for solution(s).
15
+ Get usage info for the Application and Product.
16
16
  ).strip
17
+ c.project_not_required = true
17
18
 
18
19
  # Add flag: --type [application|product|all].
19
20
  cmd_add_solntype_pickers(c)
20
21
 
21
- c.option '--[no-]all', 'Show usage for all Solutions in Business, not just Project'
22
+ c.option '--[no-]all', 'Show usage for all Solutions in Business'
22
23
  c.option(
23
24
  '--[no-]header', %(Output solution descriptions (default: true))
24
25
  )
data/lib/MrMurano/hash.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.09.07 /coding: utf-8
1
+ # Last Modified: 2017.09.27 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -113,7 +113,7 @@ module HashInit
113
113
  hash.first.each do |key, val|
114
114
  if respond_to? key
115
115
  send("#{key}=", val)
116
- else
116
+ elsif defined?(@show_errors) && @show_errors
117
117
  $stderr.puts %(HashInit: missing hash key "#{key}")
118
118
  end
119
119
  end
data/lib/MrMurano/http.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.08.24 /coding: utf-8
1
+ # Last Modified: 2017.09.20 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -108,7 +108,7 @@ module MrMurano
108
108
  # 2017-08-14: MrMurano::Account overrides the token method, and
109
109
  # it doesn't exit if no token, and then we end up here.
110
110
  ensure_token! token
111
- request['Authorization'] = 'token ' + token
111
+ request['Authorization'] = 'token ' + token unless token.to_s.empty?
112
112
  request['User-Agent'] = "MrMurano/#{MrMurano::VERSION}"
113
113
  request
114
114
  end
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.09.13 /coding: utf-8
1
+ # Last Modified: 2017.09.28 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -26,7 +26,7 @@ module MrMurano
26
26
  # '3.0.0-beta.2' is changed to '3.0.0.pre.beta.2'
27
27
  # which breaks our build (which expects the version to match herein).
28
28
  # So stick to using the '.pre.X' syntax, which ruby/gems knows.
29
- VERSION = '3.0.4'
29
+ VERSION = '3.0.5'
30
30
  EXE_NAME = File.basename($PROGRAM_NAME)
31
31
  SIGN_UP_URL = 'https://exosite.com/signup/'
32
32
  end
data/spec/Account_spec.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.09.12 /coding: utf-8
1
+ # Last Modified: 2017.09.21 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -45,6 +45,7 @@ RSpec.describe MrMurano::Account, 'token' do
45
45
  it 'Asks for nothing' do
46
46
  $cfg['user.name'] = 'bob'
47
47
  expect(@pswd).to receive(:get).once.and_return('built')
48
+ expect(@pswd).to receive(:lookup).once.and_return(nil)
48
49
 
49
50
  ret = @acc.login_info
50
51
  expect(ret).to eq(email: 'bob', password: 'built')
@@ -56,6 +57,7 @@ RSpec.describe MrMurano::Account, 'token' do
56
57
  expect(@acc).to receive(:error).once
57
58
  expect($cfg).to receive(:set).with('user.name', 'bob', :user).once.and_call_original
58
59
  expect(@pswd).to receive(:get).once.and_return('built')
60
+ expect(@pswd).to receive(:lookup).once.and_return(nil)
59
61
 
60
62
  ret = @acc.login_info
61
63
  expect(ret).to eq(email: 'bob', password: 'built')
@@ -67,6 +69,7 @@ RSpec.describe MrMurano::Account, 'token' do
67
69
  expect(@acc).to receive(:error).once
68
70
  expect($terminal).to receive(:ask).once.and_return('dog')
69
71
  expect(@pswd).to receive(:set).once.with('bizapi.hosted.exosite.io', 'bob', 'dog')
72
+ expect(@pswd).to receive(:set).once.with('bizapi.hosted.exosite.io', 'bob/twofactor', nil)
70
73
  # 2017-07-31: login_info may exit unless the command okays prompting for the password.
71
74
  # (If we don't set this, login_info exits, which we'd want to
72
75
  # catch with
data/spec/Content_spec.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.09.13 /coding: utf-8
1
+ # Last Modified: 2017.09.20 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -11,6 +11,14 @@ require 'MrMurano/Content'
11
11
  require 'MrMurano/SyncRoot'
12
12
  require '_workspace'
13
13
 
14
+ # rubocop:disable Style/HashSyntax
15
+ # - 'Use the new Ruby 1.9 hash syntax.'
16
+ # E.g.,
17
+ # :'x-amz-meta-name' => 'Solutionfile.json',
18
+ # because quoted string keys, e.g.,
19
+ # 'x-amz-meta-name': 'Solutionfile.json',
20
+ # are not supported in Ruby 2.0.
21
+
14
22
  RSpec.describe MrMurano::Content::Base do
15
23
  include_context 'WORKSPACE'
16
24
  before(:example) do
@@ -0,0 +1,124 @@
1
+ # Last Modified: 2017.09.28 /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
+
8
+ require 'fileutils'
9
+ require 'open3'
10
+ require 'pathname'
11
+ require 'cmd_common'
12
+
13
+ # NOTE: This file is a copy of, and subset of, cmd_syncdown_both_spec.rb.
14
+
15
+ RSpec.describe 'murano single sync', :cmd, :needs_password do
16
+ include_context 'CI_CMD'
17
+
18
+ before(:example) do
19
+ @applctn_name = rname('syncdownTestApp')
20
+ out, err, status = Open3.capture3(
21
+ capcmd('murano', 'application', 'create', @applctn_name, '--save')
22
+ )
23
+ expect(err).to eq('')
24
+ soln_id = out
25
+ expect(soln_id.chomp).to match(/^[a-zA-Z0-9]+$/)
26
+ expect(status.exitstatus).to eq(0)
27
+ end
28
+
29
+ after(:example) do
30
+ out, err, status = Open3.capture3(
31
+ capcmd('murano', 'solution', 'delete', '-y', @applctn_name)
32
+ )
33
+ expect(out).to eq('')
34
+ expect(err).to eq('')
35
+ expect(status.exitstatus).to eq(0)
36
+ end
37
+
38
+ context 'without ProjectFile' do
39
+ before(:example) do
40
+ FileUtils.cp_r(File.join(@testdir, 'spec/fixtures/syncable_content/.'), '.')
41
+ FileUtils.move('assets', 'files')
42
+ FileUtils.cp_r(File.join(@testdir, 'spec/fixtures/syncable_conflict/.'), '.')
43
+ end
44
+
45
+ it 'syncdown' do
46
+ out, err, status = Open3.capture3(capcmd('murano', 'syncup'))
47
+ out_lines = out.lines.map { |line| strip_fancy(line) }
48
+ expect(out_lines).to match_array(
49
+ [
50
+ "Adding item table_util\n",
51
+ a_string_starting_with('Updating item '),
52
+ "Updating item user_account\n",
53
+ "Adding item POST_/api/fire\n",
54
+ "Adding item PUT_/api/fire/{code}\n",
55
+ "Adding item DELETE_/api/fire/{code}\n",
56
+ "Adding item GET_/api/onfire\n",
57
+ "Adding item /icon.png\n",
58
+ "Adding item /\n",
59
+ "Adding item /js/script.js\n",
60
+ ]
61
+ )
62
+
63
+ expect(err).to eq('')
64
+ expect(status.exitstatus).to eq(0)
65
+
66
+ FileUtils.rm_r(%w[files modules routes services])
67
+ expect(Dir['**/*']).to eq([])
68
+
69
+ out, err, status = Open3.capture3(capcmd('murano', 'syncdown'))
70
+ out_lines = out.lines.map { |line| strip_fancy(line) }
71
+ expect(out_lines).to match_array(
72
+ [
73
+ "Adding item table_util\n",
74
+ # 2017-08-08: This says updating now because timer.timer is undeletable.
75
+ #"Adding item timer_timer\n",
76
+ "Updating item timer_timer\n",
77
+ "Adding item POST_/api/fire\n",
78
+ "Adding item DELETE_/api/fire/{code}\n",
79
+ "Adding item PUT_/api/fire/{code}\n",
80
+ "Adding item GET_/api/onfire\n",
81
+ "Adding item /js/script.js\n",
82
+ "Adding item /icon.png\n",
83
+ "Adding item /\n",
84
+ ]
85
+ )
86
+ expect(err).to eq('')
87
+ expect(status.exitstatus).to eq(0)
88
+
89
+ after = Dir['**/*'].sort
90
+ expect(after).to include(
91
+ 'files',
92
+ 'files/icon.png',
93
+ 'files/index.html',
94
+ 'files/js',
95
+ 'files/js/script.js',
96
+ 'modules',
97
+ 'modules/table_util.lua',
98
+ 'routes',
99
+ 'routes/api-fire-{code}.delete.lua',
100
+ 'routes/api-fire-{code}.put.lua',
101
+ 'routes/api-fire.post.lua',
102
+ 'routes/api-onfire.get.lua',
103
+ 'services',
104
+ 'services/timer_timer.lua',
105
+ )
106
+
107
+ # A status should show no differences.
108
+ out, err, status = Open3.capture3(capcmd('murano', 'status'))
109
+ expect(err).to eq('')
110
+ expect(out.lines).to match(
111
+ [
112
+ "Nothing new locally\n",
113
+ "Nothing new remotely\n",
114
+ "Nothing that differs\n",
115
+ "Items without a solution:\n",
116
+ " - R Resource\n",
117
+ " - I Interface\n",
118
+ ]
119
+ )
120
+ expect(status.exitstatus).to eq(0)
121
+ end
122
+ end
123
+ end
124
+
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.09.12 /coding: utf-8
1
+ # Last Modified: 2017.09.25 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -15,14 +15,18 @@ RSpec.describe 'murano syncdown', :cmd, :needs_password do
15
15
 
16
16
  before(:example) do
17
17
  @product_name = rname('syncdownTestPrd')
18
- out, err, status = Open3.capture3(capcmd('murano', 'product', 'create', @product_name, '--save'))
18
+ out, err, status = Open3.capture3(
19
+ capcmd('murano', 'product', 'create', @product_name, '--save')
20
+ )
19
21
  expect(err).to eq('')
20
22
  soln_id = out
21
23
  expect(soln_id.chomp).to match(/^[a-zA-Z0-9]+$/)
22
24
  expect(status.exitstatus).to eq(0)
23
25
 
24
26
  @applctn_name = rname('syncdownTestApp')
25
- out, err, status = Open3.capture3(capcmd('murano', 'application', 'create', @applctn_name, '--save'))
27
+ out, err, status = Open3.capture3(
28
+ capcmd('murano', 'application', 'create', @applctn_name, '--save')
29
+ )
26
30
  expect(err).to eq('')
27
31
  soln_id = out
28
32
  expect(soln_id.chomp).to match(/^[a-zA-Z0-9]+$/)
@@ -31,7 +35,9 @@ RSpec.describe 'murano syncdown', :cmd, :needs_password do
31
35
  out, err, status = Open3.capture3(capcmd('murano', 'assign', 'set'))
32
36
  #expect(out).to a_string_starting_with("Linked product #{@product_name}")
33
37
  olines = out.lines
34
- expect(strip_fancy(olines[0])).to eq("Linked '#{@product_name}' to '#{@applctn_name}'\n")
38
+ expect(strip_fancy(olines[0])).to eq(
39
+ "Linked '#{@product_name}' to '#{@applctn_name}'\n"
40
+ )
35
41
  expect(olines[1]).to eq("Created default event handler\n")
36
42
  expect(err).to eq('')
37
43
  expect(status.exitstatus).to eq(0)
@@ -40,12 +46,16 @@ RSpec.describe 'murano syncdown', :cmd, :needs_password do
40
46
  after(:example) do
41
47
  # VERIFY/2017-07-03: Skipping assign unset. Murano will clean up, right?
42
48
 
43
- out, err, status = Open3.capture3(capcmd('murano', 'solution', 'delete', '-y', @applctn_name))
49
+ out, err, status = Open3.capture3(
50
+ capcmd('murano', 'solution', 'delete', '-y', @applctn_name)
51
+ )
44
52
  expect(out).to eq('')
45
53
  expect(err).to eq('')
46
54
  expect(status.exitstatus).to eq(0)
47
55
 
48
- out, err, status = Open3.capture3(capcmd('murano', 'solution', 'delete', '--yes', @product_name))
56
+ out, err, status = Open3.capture3(
57
+ capcmd('murano', 'solution', 'delete', '--yes', @product_name)
58
+ )
49
59
  expect(out).to eq('')
50
60
  expect(err).to eq('')
51
61
  expect(status.exitstatus).to eq(0)
@@ -56,8 +66,10 @@ RSpec.describe 'murano syncdown', :cmd, :needs_password do
56
66
  FileUtils.cp_r(File.join(@testdir, 'spec/fixtures/syncable_content/.'), '.')
57
67
  FileUtils.move('assets', 'files')
58
68
  #FileUtils.mkpath('specs')
59
- #FileUtils.copy(File.join(@testdir, 'spec/fixtures/product_spec_files/lightbulb.yaml'),
60
- # 'specs/resources.yaml')
69
+ #FileUtils.copy(
70
+ # File.join(@testdir, 'spec/fixtures/product_spec_files/lightbulb.yaml'),
71
+ # 'specs/resources.yaml'
72
+ #)
61
73
  # 2017-07-03: So long as this command does not syncdown first, these
62
74
  # two files -- that conflict in name with what's on the platform --
63
75
  # won't be a problem (but would be if we synceddown first).
@@ -143,7 +155,8 @@ RSpec.describe 'murano syncdown', :cmd, :needs_password do
143
155
  'routes/api-fire-{code}.put.lua',
144
156
  'routes/api-fire.post.lua',
145
157
  'routes/api-onfire.get.lua',
146
- # 2017-07-03: services/ would not exist if we did not include fixtures/syncable_conflict/.
158
+ # 2017-07-03: services/ would not exist if we did not include
159
+ # fixtures/syncable_conflict/.
147
160
  'services',
148
161
  # 2017-07-13: No longer syncing device2_event; is internal to platform.
149
162
  #'services/device2_event.lua',
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.09.12 /coding: utf-8
1
+ # Last Modified: 2017.09.20 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -82,7 +82,7 @@ RSpec.describe 'murano syncup', :cmd, :needs_password do
82
82
  # Windows is insane:
83
83
  # "Adding item ........................Administrator.AppData.Local.Temp.2.d20170913-3860-pgji6g.project.modules.table_util\n"
84
84
  #expect(outl[5]).to eq("Adding item table_util\n")
85
- expect(outl[5]).to start_with("Adding item ")
85
+ expect(outl[5]).to start_with('Adding item ')
86
86
  expect(outl[5]).to end_with("table_util\n")
87
87
  #expect(outl[6]).to eq("Updating item c3juj9vnmec000000_event\n")
88
88
  # The order isn't always consistent, so just do start_with.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: MuranoCLI
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.4
4
+ version: 3.0.5
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: 2017-09-13 00:00:00.000000000 Z
11
+ date: 2017-09-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: certified
@@ -405,8 +405,8 @@ executables:
405
405
  extensions: []
406
406
  extra_rdoc_files: []
407
407
  files:
408
- - ".agignore"
409
408
  - ".gitignore"
409
+ - ".ignore"
410
410
  - ".rspec"
411
411
  - ".rubocop.yml"
412
412
  - ".travis.yml"
@@ -542,7 +542,8 @@ files:
542
542
  - spec/cmd_setting_application_spec.rb
543
543
  - spec/cmd_setting_product_spec.rb
544
544
  - spec/cmd_status_spec.rb
545
- - spec/cmd_syncdown_spec.rb
545
+ - spec/cmd_syncdown_application_spec.rb
546
+ - spec/cmd_syncdown_both_spec.rb
546
547
  - spec/cmd_syncup_spec.rb
547
548
  - spec/cmd_usage_spec.rb
548
549
  - spec/fixtures/.mrmuranorc