vagrant 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. data/Gemfile +2 -2
  2. data/README.md +2 -2
  3. data/Rakefile +1 -1
  4. data/VERSION +1 -1
  5. data/config/default.rb +13 -3
  6. data/lib/vagrant.rb +10 -13
  7. data/lib/vagrant/actions/base.rb +14 -2
  8. data/lib/vagrant/actions/box/download.rb +2 -7
  9. data/lib/vagrant/actions/box/verify.rb +1 -1
  10. data/lib/vagrant/actions/runner.rb +0 -1
  11. data/lib/vagrant/actions/vm/boot.rb +2 -6
  12. data/lib/vagrant/actions/vm/customize.rb +7 -5
  13. data/lib/vagrant/actions/vm/destroy.rb +4 -3
  14. data/lib/vagrant/actions/vm/down.rb +6 -3
  15. data/lib/vagrant/actions/vm/export.rb +2 -4
  16. data/lib/vagrant/actions/vm/forward_ports.rb +77 -16
  17. data/lib/vagrant/actions/vm/halt.rb +10 -2
  18. data/lib/vagrant/actions/vm/import.rb +2 -4
  19. data/lib/vagrant/actions/vm/move_hard_drive.rb +2 -2
  20. data/lib/vagrant/actions/vm/network.rb +120 -0
  21. data/lib/vagrant/actions/vm/package.rb +11 -7
  22. data/lib/vagrant/actions/vm/provision.rb +3 -3
  23. data/lib/vagrant/actions/vm/reload.rb +2 -9
  24. data/lib/vagrant/actions/vm/shared_folders.rb +19 -39
  25. data/lib/vagrant/actions/vm/start.rb +10 -2
  26. data/lib/vagrant/actions/vm/up.rb +5 -6
  27. data/lib/vagrant/active_list.rb +23 -13
  28. data/lib/vagrant/box.rb +2 -2
  29. data/lib/vagrant/busy.rb +3 -3
  30. data/lib/vagrant/command.rb +2 -2
  31. data/lib/vagrant/commands/base.rb +40 -20
  32. data/lib/vagrant/commands/destroy.rb +17 -3
  33. data/lib/vagrant/commands/halt.rb +23 -3
  34. data/lib/vagrant/commands/package.rb +54 -14
  35. data/lib/vagrant/commands/provision.rb +31 -0
  36. data/lib/vagrant/commands/reload.rb +16 -3
  37. data/lib/vagrant/commands/resume.rb +16 -3
  38. data/lib/vagrant/commands/ssh.rb +25 -3
  39. data/lib/vagrant/commands/ssh_config.rb +20 -5
  40. data/lib/vagrant/commands/status.rb +107 -40
  41. data/lib/vagrant/commands/suspend.rb +16 -3
  42. data/lib/vagrant/commands/up.rb +26 -7
  43. data/lib/vagrant/config.rb +82 -12
  44. data/lib/vagrant/downloaders/base.rb +8 -1
  45. data/lib/vagrant/downloaders/http.rb +31 -19
  46. data/lib/vagrant/environment.rb +146 -49
  47. data/lib/vagrant/provisioners/base.rb +19 -5
  48. data/lib/vagrant/provisioners/chef.rb +12 -4
  49. data/lib/vagrant/provisioners/chef_server.rb +13 -6
  50. data/lib/vagrant/provisioners/chef_solo.rb +7 -3
  51. data/lib/vagrant/resource_logger.rb +126 -0
  52. data/lib/vagrant/ssh.rb +109 -8
  53. data/lib/vagrant/systems/base.rb +70 -0
  54. data/lib/vagrant/systems/linux.rb +137 -0
  55. data/lib/vagrant/util.rb +1 -45
  56. data/lib/vagrant/util/error_helper.rb +13 -0
  57. data/lib/vagrant/util/glob_loader.rb +22 -0
  58. data/lib/vagrant/util/output_helper.rb +9 -0
  59. data/lib/vagrant/util/plain_logger.rb +12 -0
  60. data/lib/vagrant/util/platform.rb +7 -2
  61. data/lib/vagrant/util/template_renderer.rb +2 -2
  62. data/lib/vagrant/util/translator.rb +35 -0
  63. data/lib/vagrant/vm.rb +91 -10
  64. data/templates/crontab_entry.erb +1 -0
  65. data/templates/network_entry.erb +8 -0
  66. data/templates/ssh_config.erb +1 -0
  67. data/templates/{errors.yml → strings.yml} +111 -3
  68. data/templates/sync.erb +14 -0
  69. data/test/test_helper.rb +46 -3
  70. data/test/vagrant/actions/box/download_test.rb +0 -17
  71. data/test/vagrant/actions/vm/boot_test.rb +3 -10
  72. data/test/vagrant/actions/vm/customize_test.rb +6 -0
  73. data/test/vagrant/actions/vm/destroy_test.rb +6 -5
  74. data/test/vagrant/actions/vm/down_test.rb +5 -11
  75. data/test/vagrant/actions/vm/export_test.rb +1 -0
  76. data/test/vagrant/actions/vm/forward_ports_test.rb +92 -15
  77. data/test/vagrant/actions/vm/halt_test.rb +36 -4
  78. data/test/vagrant/actions/vm/import_test.rb +2 -0
  79. data/test/vagrant/actions/vm/network_test.rb +237 -0
  80. data/test/vagrant/actions/vm/package_test.rb +35 -5
  81. data/test/vagrant/actions/vm/provision_test.rb +3 -3
  82. data/test/vagrant/actions/vm/reload_test.rb +1 -1
  83. data/test/vagrant/actions/vm/shared_folders_test.rb +41 -74
  84. data/test/vagrant/actions/vm/start_test.rb +41 -3
  85. data/test/vagrant/actions/vm/up_test.rb +10 -21
  86. data/test/vagrant/active_list_test.rb +28 -43
  87. data/test/vagrant/commands/base_test.rb +25 -4
  88. data/test/vagrant/commands/destroy_test.rb +24 -12
  89. data/test/vagrant/commands/halt_test.rb +33 -11
  90. data/test/vagrant/commands/package_test.rb +77 -57
  91. data/test/vagrant/commands/provision_test.rb +50 -0
  92. data/test/vagrant/commands/reload_test.rb +27 -11
  93. data/test/vagrant/commands/resume_test.rb +25 -14
  94. data/test/vagrant/commands/ssh_config_test.rb +40 -17
  95. data/test/vagrant/commands/ssh_test.rb +52 -13
  96. data/test/vagrant/commands/status_test.rb +21 -1
  97. data/test/vagrant/commands/suspend_test.rb +25 -14
  98. data/test/vagrant/commands/up_test.rb +25 -19
  99. data/test/vagrant/config_test.rb +74 -18
  100. data/test/vagrant/downloaders/base_test.rb +2 -1
  101. data/test/vagrant/downloaders/http_test.rb +18 -8
  102. data/test/vagrant/environment_test.rb +245 -77
  103. data/test/vagrant/provisioners/base_test.rb +4 -4
  104. data/test/vagrant/provisioners/chef_server_test.rb +18 -7
  105. data/test/vagrant/provisioners/chef_solo_test.rb +17 -7
  106. data/test/vagrant/provisioners/chef_test.rb +22 -9
  107. data/test/vagrant/resource_logger_test.rb +144 -0
  108. data/test/vagrant/ssh_session_test.rb +46 -0
  109. data/test/vagrant/ssh_test.rb +42 -2
  110. data/test/vagrant/systems/linux_test.rb +174 -0
  111. data/test/vagrant/util/error_helper_test.rb +5 -0
  112. data/test/vagrant/util/output_helper_test.rb +5 -0
  113. data/test/vagrant/util/plain_logger_test.rb +17 -0
  114. data/test/vagrant/util/platform_test.rb +18 -0
  115. data/test/vagrant/util/{errors_test.rb → translator_test.rb} +25 -21
  116. data/test/vagrant/util_test.rb +12 -49
  117. data/test/vagrant/vm_test.rb +133 -11
  118. data/vagrant.gemspec +39 -15
  119. metadata +64 -40
  120. data/lib/vagrant/commands/down.rb +0 -16
  121. data/lib/vagrant/util/errors.rb +0 -36
  122. data/lib/vagrant/util/progress_meter.rb +0 -33
  123. data/test/vagrant/commands/down_test.rb +0 -17
  124. data/test/vagrant/util/progress_meter_test.rb +0 -33
@@ -1,10 +1,13 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'open-uri'
4
+ require 'uri'
5
+
1
6
  module Vagrant
2
7
  module Downloaders
3
8
  # Downloads a file from an HTTP URL to a temporary file. This
4
9
  # downloader reports its progress to stdout while downloading.
5
10
  class HTTP < Base
6
- include Util::ProgressMeter
7
-
8
11
  def self.match?(uri)
9
12
  # URI.parse barfs on '<drive letter>:\\files \on\ windows'
10
13
  # TODO temprorary
@@ -13,29 +16,38 @@ module Vagrant
13
16
  end
14
17
 
15
18
  def download!(source_url, destination_file)
16
- Net::HTTP.get_response(URI.parse(source_url)) do |response|
17
- total = response.content_length
18
- progress = 0
19
- segment_count = 0
19
+ uri = URI.parse(source_url)
20
+ http = Net::HTTP.new(uri.host, uri.port)
21
+ if uri.scheme == "https"
22
+ http.use_ssl = true
23
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
24
+ end
20
25
 
21
- response.read_body do |segment|
22
- # Report the progress out
23
- progress += segment.length
24
- segment_count += 1
26
+ http.start do |h|
27
+ h.request_get(uri.request_uri) do |response|
28
+ total = response.content_length
29
+ progress = 0
30
+ segment_count = 0
25
31
 
26
- # Progress reporting is limited to every 25 segments just so
27
- # we're not constantly updating
28
- if segment_count % 25 == 0
29
- update_progress(progress, total)
30
- segment_count = 0
31
- end
32
+ response.read_body do |segment|
33
+ # Report the progress out
34
+ progress += segment.length
35
+ segment_count += 1
36
+
37
+ # Progress reporting is limited to every 25 segments just so
38
+ # we're not constantly updating
39
+ if segment_count % 25 == 0
40
+ env.logger.report_progress(progress, total)
41
+ segment_count = 0
42
+ end
32
43
 
33
- # Store the segment
34
- destination_file.write(segment)
44
+ # Store the segment
45
+ destination_file.write(segment)
46
+ end
35
47
  end
36
48
  end
37
49
 
38
- complete_progress
50
+ env.logger.clear_progress
39
51
  end
40
52
  end
41
53
  end
@@ -5,27 +5,32 @@ module Vagrant
5
5
  class Environment
6
6
  ROOTFILE_NAME = "Vagrantfile"
7
7
  HOME_SUBDIRS = ["tmp", "boxes"]
8
+ DEFAULT_VM = :default
8
9
 
9
10
  include Util
10
11
 
12
+ attr_reader :parent # Parent environment (in the case of multi-VMs)
13
+ attr_reader :vm_name # The name of the VM (internal name) which this environment represents
14
+
11
15
  attr_accessor :cwd
12
16
  attr_reader :root_path
13
17
  attr_reader :config
14
18
  attr_reader :box
15
19
  attr_accessor :vm
16
- attr_reader :ssh
20
+ attr_reader :vms
17
21
  attr_reader :active_list
18
22
  attr_reader :commands
23
+ attr_reader :logger
19
24
 
20
25
  #---------------------------------------------------------------
21
26
  # Class Methods
22
27
  #---------------------------------------------------------------
23
- class <<self
28
+ class << self
24
29
  # Loads and returns an environment given a specific working
25
30
  # directory. If a working directory is not given, it will default
26
31
  # to the pwd.
27
32
  def load!(cwd=nil)
28
- Environment.new(cwd).load!
33
+ Environment.new(:cwd => cwd).load!
29
34
  end
30
35
 
31
36
  # Verifies that VirtualBox is installed and that the version of
@@ -43,12 +48,23 @@ module Vagrant
43
48
  end
44
49
  end
45
50
 
46
- def initialize(cwd=nil)
47
- @cwd = cwd
51
+ def initialize(opts=nil)
52
+ defaults = {
53
+ :parent => nil,
54
+ :vm_name => nil,
55
+ :vm => nil,
56
+ :cwd => nil
57
+ }
58
+
59
+ opts = defaults.merge(opts || {})
60
+
61
+ defaults.each do |key, value|
62
+ instance_variable_set("@#{key}".to_sym, opts[key])
63
+ end
48
64
  end
49
65
 
50
66
  #---------------------------------------------------------------
51
- # Path Helpers
67
+ # Helpers
52
68
  #---------------------------------------------------------------
53
69
 
54
70
  # Specifies the "current working directory" for this environment.
@@ -62,7 +78,7 @@ module Vagrant
62
78
  # The path to the `dotfile`, which contains the persisted UUID of
63
79
  # the VM if it exists.
64
80
  def dotfile_path
65
- File.join(root_path, config.vagrant.dotfile_name)
81
+ root_path ? File.join(root_path, config.vagrant.dotfile_name) : nil
66
82
  end
67
83
 
68
84
  # The path to the home directory, which is usually in `~/.vagrant/~
@@ -80,6 +96,34 @@ module Vagrant
80
96
  File.join(home_path, "boxes")
81
97
  end
82
98
 
99
+ # Returns the VMs associated with this environment.
100
+ def vms
101
+ @vms ||= {}
102
+ end
103
+
104
+ # Returns the primray VM associated with this environment
105
+ def primary_vm
106
+ return vms.values.first if !multivm?
107
+ return parent.primary_vm if parent
108
+
109
+ config.vm.defined_vms.each do |name, subvm|
110
+ return vms[name] if subvm.options[:primary]
111
+ end
112
+
113
+ nil
114
+ end
115
+
116
+ # Returns a boolean whether this environment represents a multi-VM
117
+ # environment or not. This will work even when called on child
118
+ # environments.
119
+ def multivm?
120
+ if parent
121
+ parent.multivm?
122
+ else
123
+ vms.length > 1
124
+ end
125
+ end
126
+
83
127
  #---------------------------------------------------------------
84
128
  # Load Methods
85
129
  #---------------------------------------------------------------
@@ -88,6 +132,7 @@ module Vagrant
88
132
  # such as `vm`, `config`, etc. on this environment. The order this
89
133
  # method calls its other methods is very particular.
90
134
  def load!
135
+ load_logger!
91
136
  load_root_path!
92
137
  load_config!
93
138
  load_home_directory!
@@ -95,7 +140,6 @@ module Vagrant
95
140
  load_config!
96
141
  self.class.check_virtualbox!
97
142
  load_vm!
98
- load_ssh!
99
143
  load_active_list!
100
144
  load_commands!
101
145
  self
@@ -125,25 +169,54 @@ module Vagrant
125
169
  # this environment, meaning that it will use the given root directory
126
170
  # to load the Vagrantfile into that context.
127
171
  def load_config!
128
- # Prepare load paths for config files
129
- load_paths = [File.join(PROJECT_ROOT, "config", "default.rb")]
130
- load_paths << File.join(box.directory, ROOTFILE_NAME) if box
131
- load_paths << File.join(home_path, ROOTFILE_NAME) if home_path
132
- load_paths << File.join(root_path, ROOTFILE_NAME) if root_path
172
+ # Prepare load paths for config files and append to config queue
173
+ config_queue = [File.join(PROJECT_ROOT, "config", "default.rb")]
174
+ config_queue << File.join(box.directory, ROOTFILE_NAME) if box
175
+ config_queue << File.join(home_path, ROOTFILE_NAME) if home_path
176
+ config_queue << File.join(root_path, ROOTFILE_NAME) if root_path
177
+
178
+ # If this environment represents some VM in a multi-VM environment,
179
+ # we push that VM's configuration onto the config_queue.
180
+ if vm_name
181
+ subvm = parent.config.vm.defined_vms[vm_name]
182
+ config_queue << subvm.proc_stack if subvm
183
+ end
184
+
185
+ # Flatten the config queue so any nested procs are flattened
186
+ config_queue.flatten!
133
187
 
134
188
  # Clear out the old data
135
189
  Config.reset!(self)
136
190
 
137
191
  # Load each of the config files in order
138
- load_paths.each do |path|
139
- if File.exist?(path)
140
- logger.info "Loading config from #{path}..."
141
- load path
192
+ config_queue.each do |item|
193
+ if item.is_a?(String) && File.exist?(item)
194
+ load item
195
+ next
196
+ end
197
+
198
+ if item.is_a?(Proc)
199
+ # Just push the proc straight onto the config runnable stack
200
+ Config.run(&item)
142
201
  end
143
202
  end
144
203
 
145
204
  # Execute the configuration stack and store the result
146
205
  @config = Config.execute!
206
+
207
+ # (re)load the logger
208
+ load_logger!
209
+ end
210
+
211
+ # Loads the logger for this environment. This is called by
212
+ # {#load_config!} so that the logger is only loaded after
213
+ # configuration information is available. The logger is also
214
+ # loaded early on in the load chain process so that the various
215
+ # references to logger won't throw nil exceptions, but by default
216
+ # the logger will just send the log data to a black hole.
217
+ def load_logger!
218
+ resource = vm_name || "vagrant"
219
+ @logger = ResourceLogger.new(resource, self)
147
220
  end
148
221
 
149
222
  # Loads the home directory path and creates the necessary subdirectories
@@ -171,19 +244,49 @@ module Vagrant
171
244
 
172
245
  # Loads the persisted VM (if it exists) for this environment.
173
246
  def load_vm!
174
- return if !root_path || !File.file?(dotfile_path)
247
+ # This environment represents a single sub VM. The VM is then
248
+ # probably (read: should be) set on the VM attribute, so we do
249
+ # nothing.
250
+ return if vm_name
251
+
252
+ # First load the defaults (blank, noncreated VMs)
253
+ load_blank_vms!
175
254
 
255
+ # If we have no dotfile, then return
256
+ return if !dotfile_path || !File.file?(dotfile_path)
257
+
258
+ # Open and parse the dotfile
176
259
  File.open(dotfile_path) do |f|
177
- @vm = Vagrant::VM.find(f.read)
178
- @vm.env = self if @vm
260
+ data = { DEFAULT_VM => f.read }
261
+
262
+ begin
263
+ data = JSON.parse(data[DEFAULT_VM])
264
+ rescue JSON::ParserError
265
+ # Most likely an older (<= 0.3.x) dotfile. Try to load it
266
+ # as the :__vagrant VM.
267
+ end
268
+
269
+ data.each do |key, value|
270
+ key = key.to_sym
271
+ vms[key] = Vagrant::VM.find(value, self, key)
272
+ end
179
273
  end
180
274
  rescue Errno::ENOENT
181
- @vm = nil
275
+ # Just rescue it.
182
276
  end
183
277
 
184
- # Loads/initializes the SSH object
185
- def load_ssh!
186
- @ssh = SSH.new(self)
278
+ # Loads blank VMs into the `vms` attribute.
279
+ def load_blank_vms!
280
+ # Clear existing vms
281
+ vms.clear
282
+
283
+ # Load up the blank VMs
284
+ defined_vms = config.vm.defined_vms.keys
285
+ defined_vms = [DEFAULT_VM] if defined_vms.empty?
286
+
287
+ defined_vms.each do |name|
288
+ vms[name] = Vagrant::VM.new(:vm_name => name, :env => self)
289
+ end
187
290
  end
188
291
 
189
292
  # Loads the activelist for this environment
@@ -202,34 +305,28 @@ module Vagrant
202
305
  # Methods to manage VM
203
306
  #---------------------------------------------------------------
204
307
 
205
- # Sets the VM to a new VM. This is not too useful but is used
206
- # in {Command.up}. This will very likely be refactored at a later
207
- # time.
208
- def create_vm
209
- @vm = VM.new
210
- @vm.env = self
211
- @vm
212
- end
213
-
214
308
  # Persists this environment's VM to the dotfile so it can be
215
309
  # re-loaded at a later time.
216
- def persist_vm
217
- # Save to the dotfile for this project
218
- File.open(dotfile_path, 'w+') do |f|
219
- f.write(vm.uuid)
310
+ def update_dotfile
311
+ return parent.update_dotfile if parent
312
+
313
+ # Generate and save the persisted VM info
314
+ data = vms.inject({}) do |acc, data|
315
+ key, value = data
316
+ acc[key] = value.uuid if value.created?
317
+ acc
220
318
  end
221
319
 
222
- # Also add to the global store
223
- active_list.add(vm)
224
- end
225
-
226
- # Removes this environment's VM from the dotfile.
227
- def depersist_vm
228
- # Delete the dotfile if it exists
229
- File.delete(dotfile_path) if File.exist?(dotfile_path)
320
+ if data.empty?
321
+ File.delete(dotfile_path)
322
+ else
323
+ File.open(dotfile_path, 'w+') do |f|
324
+ f.write(data.to_json)
325
+ end
326
+ end
230
327
 
231
- # Remove from the global store
232
- active_list.remove(vm)
328
+ # Also add to the global store
329
+ # active_list.add(vm)
233
330
  end
234
331
 
235
332
  #---------------------------------------------------------------
@@ -244,10 +341,10 @@ module Vagrant
244
341
  require_root_path
245
342
 
246
343
  if !box
247
- if !Vagrant.config.vm.box
344
+ if !config.vm.box
248
345
  error_and_exit(:box_not_specified)
249
346
  else
250
- error_and_exit(:box_specified_doesnt_exist, :box_name => Vagrant.config.vm.box)
347
+ error_and_exit(:box_specified_doesnt_exist, :box_name => config.vm.box)
251
348
  end
252
349
  end
253
350
  end
@@ -7,11 +7,25 @@ module Vagrant
7
7
  class Base
8
8
  include Vagrant::Util
9
9
 
10
- # The environment which this is being provisioned in
11
- attr_reader :env
10
+ # The VM which this is being provisioned for
11
+ attr_reader :vm
12
12
 
13
- def initialize(env)
14
- @env = env
13
+ def initialize(vm)
14
+ @vm = vm
15
+ end
16
+
17
+ # This method returns the environment which the provisioner is working
18
+ # on. This is also the environment of the VM. This method is provided
19
+ # as a simple helper since the environment is often used throughout the
20
+ # provisioner.
21
+ def env
22
+ @vm.env
23
+ end
24
+
25
+ # This method returns the environment's logger as a convenience
26
+ # method.
27
+ def logger
28
+ env.logger
15
29
  end
16
30
 
17
31
  # This is the method called to "prepare" the provisioner. This is called
@@ -26,4 +40,4 @@ module Vagrant
26
40
  def provision!; end
27
41
  end
28
42
  end
29
- end
43
+ end
@@ -75,9 +75,17 @@ module Vagrant
75
75
  raise Actions::ActionException.new(:chef_base_invalid_provisioner)
76
76
  end
77
77
 
78
+ def verify_binary(binary)
79
+ vm.ssh.execute do |ssh|
80
+ # Checks for the existence of chef binary and error if it
81
+ # doesn't exist.
82
+ ssh.exec!("which #{binary}", :error_key => :chef_not_detected, :error_data => {:binary => binary})
83
+ end
84
+ end
85
+
78
86
  def chown_provisioning_folder
79
87
  logger.info "Setting permissions on chef provisioning folder..."
80
- env.ssh.execute do |ssh|
88
+ vm.ssh.execute do |ssh|
81
89
  ssh.exec!("sudo mkdir -p #{env.config.chef.provisioning_path}")
82
90
  ssh.exec!("sudo chown #{env.config.ssh.username} #{env.config.chef.provisioning_path}")
83
91
  end
@@ -89,7 +97,7 @@ module Vagrant
89
97
  }.merge(template_vars))
90
98
 
91
99
  logger.info "Uploading chef configuration script..."
92
- env.ssh.upload!(StringIO.new(config_file), File.join(env.config.chef.provisioning_path, filename))
100
+ vm.ssh.upload!(StringIO.new(config_file), File.join(env.config.chef.provisioning_path, filename))
93
101
  end
94
102
 
95
103
  def setup_json
@@ -98,7 +106,7 @@ module Vagrant
98
106
  # Set up initial configuration
99
107
  data = {
100
108
  :config => env.config,
101
- :directory => env.config.vm.project_directory,
109
+ :directory => env.config.vm.shared_folders["v-root"][:guestpath],
102
110
  }
103
111
 
104
112
  # And wrap it under the "vagrant" namespace
@@ -110,7 +118,7 @@ module Vagrant
110
118
 
111
119
  json = data.to_json
112
120
 
113
- env.ssh.upload!(StringIO.new(json), File.join(env.config.chef.provisioning_path, "dna.json"))
121
+ vm.ssh.upload!(StringIO.new(json), File.join(env.config.chef.provisioning_path, "dna.json"))
114
122
  end
115
123
  end
116
124
  end