vagrant-unbundled 2.2.10.0 → 2.2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -0
  3. data/Gemfile +1 -1
  4. data/README.md +4 -44
  5. data/RELEASE.md +1 -1
  6. data/contrib/zsh/_vagrant +3 -1
  7. data/contrib/zsh/generate_zsh_completion.rb +2 -3
  8. data/lib/vagrant.rb +0 -4
  9. data/lib/vagrant/action/builder.rb +6 -15
  10. data/lib/vagrant/action/builtin/box_add.rb +5 -1
  11. data/lib/vagrant/action/builtin/cloud_init_setup.rb +10 -15
  12. data/lib/vagrant/action/builtin/synced_folders.rb +8 -2
  13. data/lib/vagrant/action/runner.rb +1 -1
  14. data/lib/vagrant/box.rb +8 -2
  15. data/lib/vagrant/box_collection.rb +1 -1
  16. data/lib/vagrant/bundler.rb +43 -16
  17. data/lib/vagrant/machine.rb +8 -5
  18. data/lib/vagrant/machine_index.rb +1 -0
  19. data/lib/vagrant/plugin/v2/command.rb +2 -1
  20. data/lib/vagrant/shared_helpers.rb +8 -0
  21. data/lib/vagrant/util/downloader.rb +3 -2
  22. data/lib/vagrant/util/is_port_open.rb +1 -1
  23. data/lib/vagrant/util/mime.rb +92 -0
  24. data/lib/vagrant/util/platform.rb +2 -1
  25. data/lib/vagrant/util/template_renderer.rb +2 -2
  26. data/lib/vagrant/util/uploader.rb +7 -4
  27. data/plugins/commands/cap/command.rb +5 -1
  28. data/plugins/commands/cloud/auth/login.rb +20 -23
  29. data/plugins/commands/cloud/auth/logout.rb +2 -10
  30. data/plugins/commands/cloud/auth/middleware/add_downloader_authentication.rb +57 -0
  31. data/plugins/commands/cloud/auth/whoami.rb +18 -20
  32. data/plugins/commands/cloud/box/create.rb +33 -29
  33. data/plugins/commands/cloud/box/delete.rb +30 -24
  34. data/plugins/commands/cloud/box/show.rb +41 -31
  35. data/plugins/commands/cloud/box/update.rb +34 -26
  36. data/plugins/commands/cloud/client/client.rb +50 -81
  37. data/plugins/commands/cloud/list.rb +3 -4
  38. data/plugins/commands/cloud/locales/en.yml +9 -9
  39. data/plugins/commands/cloud/plugin.rb +10 -0
  40. data/plugins/commands/cloud/provider/create.rb +38 -28
  41. data/plugins/commands/cloud/provider/delete.rb +39 -29
  42. data/plugins/commands/cloud/provider/update.rb +37 -28
  43. data/plugins/commands/cloud/provider/upload.rb +44 -34
  44. data/plugins/commands/cloud/publish.rb +185 -108
  45. data/plugins/commands/cloud/search.rb +34 -21
  46. data/plugins/commands/cloud/util.rb +266 -162
  47. data/plugins/commands/cloud/version/create.rb +33 -28
  48. data/plugins/commands/cloud/version/delete.rb +35 -28
  49. data/plugins/commands/cloud/version/release.rb +35 -29
  50. data/plugins/commands/cloud/version/revoke.rb +36 -29
  51. data/plugins/commands/cloud/version/update.rb +29 -25
  52. data/plugins/commands/login/plugin.rb +0 -13
  53. data/plugins/guests/arch/cap/smb.rb +1 -1
  54. data/plugins/guests/darwin/cap/darwin_version.rb +40 -0
  55. data/plugins/guests/darwin/cap/mount_smb_shared_folder.rb +1 -1
  56. data/plugins/guests/darwin/cap/mount_vmware_shared_folder.rb +12 -2
  57. data/plugins/guests/darwin/plugin.rb +10 -0
  58. data/plugins/guests/debian/cap/change_host_name.rb +8 -7
  59. data/plugins/guests/linux/cap/mount_smb_shared_folder.rb +16 -41
  60. data/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb +6 -0
  61. data/plugins/guests/linux/cap/persist_mount_shared_folder.rb +18 -5
  62. data/plugins/guests/linux/cap/reboot.rb +10 -5
  63. data/plugins/guests/redhat/cap/change_host_name.rb +6 -2
  64. data/plugins/guests/suse/cap/change_host_name.rb +32 -11
  65. data/plugins/guests/windows/cap/reboot.rb +8 -4
  66. data/plugins/kernel_v2/config/cloud_init.rb +7 -0
  67. data/plugins/kernel_v2/config/disk.rb +1 -1
  68. data/plugins/kernel_v2/config/vm.rb +5 -4
  69. data/plugins/providers/hyperv/action.rb +1 -1
  70. data/plugins/providers/virtualbox/cap/mount_options.rb +1 -1
  71. data/plugins/providers/virtualbox/model/storage_controller_array.rb +4 -6
  72. data/plugins/providers/virtualbox/provider.rb +2 -1
  73. data/plugins/synced_folders/smb/cap/mount_options.rb +21 -1
  74. data/plugins/synced_folders/smb/plugin.rb +10 -0
  75. data/scripts/website_push_www.sh +1 -1
  76. data/vagrant.gemspec +5 -6
  77. data/version.txt +1 -1
  78. metadata +1202 -1595
  79. data/plugins/commands/login/client.rb +0 -253
  80. data/plugins/commands/login/command.rb +0 -137
  81. data/plugins/commands/login/errors.rb +0 -24
  82. data/plugins/commands/login/locales/en.yml +0 -49
  83. data/scripts/website_push_docs.sh +0 -40
@@ -5,11 +5,13 @@ module VagrantPlugins
5
5
  module CloudCommand
6
6
  module Command
7
7
  class Publish < Vagrant.plugin("2", :command)
8
+ include Util
9
+
8
10
  def execute
9
- options = {}
11
+ options = {direct_upload: true}
10
12
 
11
13
  opts = OptionParser.new do |o|
12
- o.banner = "Usage: vagrant cloud publish [options] organization/box-name version provider-name provider-file"
14
+ o.banner = "Usage: vagrant cloud publish [options] organization/box-name version provider-name [provider-file]"
13
15
  o.separator ""
14
16
  o.separator "Create and release a new Vagrant Box on Vagrant Cloud"
15
17
  o.separator ""
@@ -19,7 +21,7 @@ module VagrantPlugins
19
21
  o.on("--box-version VERSION", String, "Version of box to create") do |v|
20
22
  options[:box_version] = v
21
23
  end
22
- o.on("--url URL", String, "Remote URL to download this provider") do |u|
24
+ o.on("--url URL", String, "Remote URL to download this provider (cannot be used with provider-file)") do |u|
23
25
  options[:url] = u
24
26
  end
25
27
  o.on("-d", "--description DESCRIPTION", String, "Full description of box") do |d|
@@ -28,154 +30,229 @@ module VagrantPlugins
28
30
  o.on("--version-description DESCRIPTION", String, "Description of the version to create") do |v|
29
31
  options[:version_description] = v
30
32
  end
31
- o.on("-f", "--force", "Disables confirmation to create or update box") do |f|
33
+ o.on("-f", "--[no-]force", "Disables confirmation to create or update box") do |f|
32
34
  options[:force] = f
33
35
  end
34
- o.on("-p", "--private", "Makes box private") do |p|
36
+ o.on("-p", "--[no-]private", "Makes box private") do |p|
35
37
  options[:private] = p
36
38
  end
37
- o.on("-r", "--release", "Releases box") do |p|
39
+ o.on("-r", "--[no-]release", "Releases box") do |p|
38
40
  options[:release] = p
39
41
  end
40
42
  o.on("-s", "--short-description DESCRIPTION", String, "Short description of the box") do |s|
41
43
  options[:short_description] = s
42
44
  end
43
- o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address") do |u|
44
- options[:username] = u
45
- end
46
45
  o.on("-c", "--checksum CHECKSUM_VALUE", String, "Checksum of the box for this provider. --checksum-type option is required.") do |c|
47
46
  options[:checksum] = c
48
47
  end
49
48
  o.on("-C", "--checksum-type TYPE", String, "Type of checksum used (md5, sha1, sha256, sha384, sha512). --checksum option is required.") do |c|
50
49
  options[:checksum_type] = c
51
50
  end
51
+ o.on("--[no-]direct-upload", "Upload asset directly to backend storage") do |d|
52
+ options[:direct_upload] = d
53
+ end
52
54
  end
53
55
 
54
56
  # Parse the options
55
57
  argv = parse_options(opts)
56
58
  return if !argv
57
59
 
58
- if argv.empty? || argv.length > 4 || argv.length < 3 || (argv.length == 3 && !options[:url])
60
+ if argv.length < 3 || # missing required arguments
61
+ argv.length > 4 || # too many arguments
62
+ (argv.length < 4 && !options.key?(:url)) || # file argument required if url is not provided
63
+ (argv.length > 3 && options.key?(:url)) # cannot provider url and file argument
59
64
  raise Vagrant::Errors::CLIInvalidUsage,
60
65
  help: opts.help.chomp
61
66
  end
62
67
 
63
- box = argv.first.split('/', 2)
64
- org = box[0]
65
- box_name = box[1]
66
- version = argv[1]
67
- provider_name = argv[2]
68
- box_file = argv[3]
68
+ org, box_name = argv.first.split('/', 2)
69
+ _, version, provider_name, box_file = argv
69
70
 
70
- if !options[:url] && !File.file?(box_file)
71
+ if box_file && !File.file?(box_file)
71
72
  raise Vagrant::Errors::BoxFileNotExist,
72
73
  file: box_file
73
74
  end
74
75
 
75
- @client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username])
76
+ @client = client_login(@env)
77
+ params = options.slice(:private, :release, :url, :short_description,
78
+ :description, :version_description, :checksum, :checksum_type)
76
79
 
77
- publish_box(org, box_name, version, provider_name, box_file, options, @client.token)
78
- end
79
-
80
- def publish_box(org, box_name, version, provider_name, box_file, options, access_token)
81
- server_url = VagrantPlugins::CloudCommand::Util.api_server_url
82
-
83
- @env.ui.warn(I18n.t("cloud_command.publish.confirm.warn"))
84
-
85
- @env.ui.info(I18n.t("cloud_command.publish.confirm.box", org: org,
86
- box_name: box_name, version: version, provider_name: provider_name))
87
- @env.ui.info(I18n.t("cloud_command.publish.confirm.private")) if options[:private]
88
- @env.ui.info(I18n.t("cloud_command.publish.confirm.release")) if options[:release]
89
- @env.ui.info(I18n.t("cloud_command.publish.confirm.box_url",
90
- url: options[:url])) if options[:url]
91
- @env.ui.info(I18n.t("cloud_command.publish.confirm.box_description",
92
- description: options[:description])) if options[:description]
93
- @env.ui.info(I18n.t("cloud_command.publish.confirm.box_short_desc",
94
- short_description: options[:short_description])) if options[:short_description]
95
- @env.ui.info(I18n.t("cloud_command.publish.confirm.version_desc",
96
- version_description: options[:version_description])) if options[:version_description]
80
+ # Display output to user describing action to be taken
81
+ display_preamble(org, box_name, version, provider_name, params)
97
82
 
98
83
  if !options[:force]
99
84
  cont = @env.ui.ask(I18n.t("cloud_command.continue"))
100
85
  return 1 if cont.strip.downcase != "y"
101
86
  end
102
87
 
103
- account = VagrantPlugins::CloudCommand::Util.account(org, access_token, server_url)
104
- box = VagrantCloud::Box.new(account, box_name, nil, options[:short_description], options[:description], access_token)
105
- cloud_version = VagrantCloud::Version.new(box, version, nil, options[:version_description], access_token)
106
- provider = VagrantCloud::Provider.new(cloud_version, provider_name, nil, options[:url], org, box_name,
107
- access_token, nil, options[:checksum], options[:checksum_type])
108
-
109
- ui = Vagrant::UI::Prefixed.new(@env.ui, "cloud")
110
-
111
- begin
112
- ui.info(I18n.t("cloud_command.publish.box_create"))
113
- box.create
114
- rescue VagrantCloud::ClientError => e
115
- if e.error_code == 422
116
- ui.warn(I18n.t("cloud_command.publish.update_continue", obj: "Box"))
117
- box.update(options)
118
- else
119
- @env.ui.error(I18n.t("cloud_command.errors.publish.fail", org: org, box_name: box_name))
120
- @env.ui.error(e)
121
- return 1
122
- end
123
- end
88
+ # Load up all the models we'll need to publish the asset
89
+ box = load_box(org, box_name, @client.token)
90
+ box_v = load_box_version(box, version)
91
+ box_p = load_version_provider(box_v, provider_name)
124
92
 
125
- begin
126
- ui.info(I18n.t("cloud_command.publish.version_create"))
127
- cloud_version.create_version
128
- rescue VagrantCloud::ClientError => e
129
- if e.error_code == 422
130
- ui.warn(I18n.t("cloud_command.publish.update_continue", obj: "Version"))
131
- cloud_version.update
132
- else
133
- @env.ui.error(I18n.t("cloud_command.errors.publish.fail", org: org, box_name: box_name))
134
- @env.ui.error(e)
135
- return 1
136
- end
137
- rescue VagrantCloud::InvalidVersion => e
138
- @env.ui.error(I18n.t("cloud_command.errors.publish.fail", org: org, box_name: box_name))
139
- @env.ui.error(e)
140
- return 1
93
+ # Update all the data
94
+ set_box_info(box, params.slice(:private, :short_description, :description))
95
+ set_version_info(box_v, params.slice(:version_description))
96
+ set_provider_info(box_p, params.slice(:checksum, :checksum_type, :url))
97
+
98
+ # Save any updated state
99
+ @env.ui.warn(I18n.t("cloud_command.publish.box_save"))
100
+ box.save
101
+
102
+ # If we have a box file asset, upload it
103
+ if box_file
104
+ upload_box_file(box_p, box_file, options.slice(:direct_upload))
141
105
  end
142
106
 
143
- begin
144
- ui.info(I18n.t("cloud_command.publish.provider_create"))
145
- provider.create_provider
146
- rescue VagrantCloud::ClientError => e
147
- if e.error_code == 422
148
- ui.warn(I18n.t("cloud_command.publish.update_continue", obj: "Provider"))
149
- provider.update
150
- else
151
- @env.ui.error(I18n.t("cloud_command.errors.publish.fail", org: org, box_name: box_name))
152
- @env.ui.error(e)
153
- return 1
154
- end
107
+ # If configured to release the box, release it
108
+ if options[:release] && !box_v.released?
109
+ release_version(box_v)
155
110
  end
156
111
 
157
- begin
158
- if !options[:url]
159
- box_file = File.absolute_path(box_file)
160
- ui.info(I18n.t("cloud_command.publish.upload_provider", file: box_file))
161
- ul = Vagrant::Util::Uploader.new(provider.upload_url, box_file, ui: @env.ui)
162
- ul.upload!
163
- end
164
- if options[:release]
165
- ui.info(I18n.t("cloud_command.publish.release"))
166
- cloud_version.release
167
- end
168
- @env.ui.success(I18n.t("cloud_command.publish.complete", org: org, box_name: box_name))
169
- success = box.read(org, box_name)
170
- success = success.delete_if{|_, v|v.nil?}
171
- VagrantPlugins::CloudCommand::Util.format_box_results(success, @env)
172
- return 0
173
- rescue Vagrant::Errors::UploaderError, VagrantCloud::ClientError => e
174
- @env.ui.error(I18n.t("cloud_command.errors.publish.fail", org: org, box_name: box_name))
175
- @env.ui.error(e)
176
- return 1
112
+ # And we're done!
113
+ @env.ui.success(I18n.t("cloud_command.publish.complete", org: org, box_name: box_name))
114
+ format_box_results(box, @env)
115
+ 0
116
+ rescue VagrantCloud::Error => err
117
+ @env.ui.error(I18n.t("cloud_command.errors.publish.fail", org: org, box_name: box_name))
118
+ @env.ui.error(err.message)
119
+ 1
120
+ end
121
+
122
+ # Upload the file for the given box provider
123
+ #
124
+ # @param [VagrantCloud::Box::Provider] provider Vagrant Cloud box version provider
125
+ # @param [String] box_file Path to local asset for upload
126
+ # @param [Hash] options
127
+ # @option options [Boolean] :direct_upload Upload directly to backend storage
128
+ # @return [nil]
129
+ def upload_box_file(provider, box_file, options={})
130
+ box_file = File.absolute_path(box_file)
131
+ @env.ui.info(I18n.t("cloud_command.publish.upload_provider", file: box_file))
132
+ provider.upload(direct: options[:direct_upload]) do |upload_url|
133
+ Vagrant::Util::Uploader.new(upload_url, box_file, ui: @env.ui, method: :put).upload!
177
134
  end
178
- return 1
135
+ nil
136
+ end
137
+
138
+ # Release the box version
139
+ #
140
+ # @param [VagrantCloud::Box::Version] version Vagrant Cloud box version
141
+ # @return [nil]
142
+ def release_version(version)
143
+ @env.ui.info(I18n.t("cloud_command.publish.release"))
144
+ version.release
145
+ nil
146
+ end
147
+
148
+ # Set any box related attributes that were provided
149
+ #
150
+ # @param [VagrantCloud::Box] box Vagrant Cloud box
151
+ # @param [Hash] options
152
+ # @option options [Boolean] :private Box access is private
153
+ # @option options [String] :short_description Short description of box
154
+ # @option options [String] :description Full description of box
155
+ # @return [VagrantCloud::Box]
156
+ def set_box_info(box, options={})
157
+ box.private = options[:private] if options.key?(:private)
158
+ box.short_description = options[:short_description] if options.key?(:short_description)
159
+ box.description = options[:description] if options.key?(:description)
160
+ box
161
+ end
162
+
163
+ # Set any version related attributes that were provided
164
+ #
165
+ # @param [VagrantCloud::Box::Version] version Vagrant Cloud box version
166
+ # @param [Hash] options
167
+ # @option options [String] :version_description Description for this version
168
+ # @return [VagrantCloud::Box::Version]
169
+ def set_version_info(version, options={})
170
+ version.description = options[:version_description] if options.key?(:version_description)
171
+ version
172
+ end
173
+
174
+ # Set any provider related attributes that were provided
175
+ #
176
+ # @param [VagrantCloud::Box::Provider] provider Vagrant Cloud box version provider
177
+ # @param [Hash] options
178
+ # @option options [String] :url Remote URL for self hosted
179
+ # @option options [String] :checksum_type Type of checksum value provided
180
+ # @option options [String] :checksum Checksum of the box asset
181
+ # @return [VagrantCloud::Box::Provider]
182
+ def set_provider_info(provider, options={})
183
+ provider.url = options[:url] if options.key?(:url)
184
+ provider.checksum_type = options[:checksum_type] if options.key?(:checksum_type)
185
+ provider.checksum = options[:checksum] if options.key?(:checksum)
186
+ provider
187
+ end
188
+
189
+ # Load the requested version provider
190
+ #
191
+ # @param [VagrantCloud::Box::Version] version The version of the Vagrant Cloud box
192
+ # @param [String] provider_name Name of the provider
193
+ # @return [VagrantCloud::Box::Provider]
194
+ def load_version_provider(version, provider_name)
195
+ provider = version.providers.detect { |pv| pv.name == provider_name }
196
+ return provider if provider
197
+ version.add_provider(provider_name)
198
+ end
199
+
200
+ # Load the requested box version
201
+ #
202
+ # @param [VagrantCloud::Box] box The Vagrant Cloud box
203
+ # @param [String] version Version of the box
204
+ # @return [VagrantCloud::Box::Version]
205
+ def load_box_version(box, version)
206
+ v = box.versions.detect { |v| v.version == version }
207
+ return v if v
208
+ box.add_version(version)
209
+ end
210
+
211
+ # Load the requested box
212
+ #
213
+ # @param [String] org Organization name for box
214
+ # @param [String] box_name Name of the box
215
+ # @param [String] access_token User access token
216
+ # @return [VagrantCloud::Box]
217
+ def load_box(org, box_name, access_token)
218
+ account = VagrantCloud::Account.new(
219
+ custom_server: api_server_url,
220
+ access_token: access_token
221
+ )
222
+ box = account.organization(name: org).boxes.detect { |b| b.name == box_name }
223
+ return box if box
224
+ account.organization(name: org).add_box(box_name)
225
+ end
226
+
227
+ # Display publishing information to user before starting process
228
+ #
229
+ # @param [String] org Organization name
230
+ # @param [String] box_name Name of the box to publish
231
+ # @param [String] version Version of the box to publish
232
+ # @param [String] provider_name Name of the provider being published
233
+ # @param [Hash] options
234
+ # @option options [Boolean] :private Box is private
235
+ # @option options [Boolean] :release Box should be released
236
+ # @option options [String] :url Remote URL for self-hosted boxes
237
+ # @option options [String] :description Description of the box
238
+ # @option options [String] :short_description Short description of the box
239
+ # @option options [String] :version_description Description of the box version
240
+ # @return [nil]
241
+ def display_preamble(org, box_name, version, provider_name, options={})
242
+ @env.ui.warn(I18n.t("cloud_command.publish.confirm.warn"))
243
+ @env.ui.info(I18n.t("cloud_command.publish.confirm.box", org: org,
244
+ box_name: box_name, version: version, provider_name: provider_name))
245
+ @env.ui.info(I18n.t("cloud_command.publish.confirm.private")) if options[:private]
246
+ @env.ui.info(I18n.t("cloud_command.publish.confirm.release")) if options[:release]
247
+ @env.ui.info(I18n.t("cloud_command.publish.confirm.box_url",
248
+ url: options[:url])) if options[:url]
249
+ @env.ui.info(I18n.t("cloud_command.publish.confirm.box_description",
250
+ description: options[:description])) if options[:description]
251
+ @env.ui.info(I18n.t("cloud_command.publish.confirm.box_short_desc",
252
+ short_description: options[:short_description])) if options[:short_description]
253
+ @env.ui.info(I18n.t("cloud_command.publish.confirm.version_desc",
254
+ version_description: options[:version_description])) if options[:version_description]
255
+ nil
179
256
  end
180
257
  end
181
258
  end
@@ -4,8 +4,10 @@ module VagrantPlugins
4
4
  module CloudCommand
5
5
  module Command
6
6
  class Search < Vagrant.plugin("2", :command)
7
+ include Util
8
+
7
9
  def execute
8
- options = {}
10
+ options = {quiet: true}
9
11
 
10
12
  opts = OptionParser.new do |o|
11
13
  o.banner = "Usage: vagrant cloud search [options] query"
@@ -37,45 +39,56 @@ module VagrantPlugins
37
39
  o.on("--sort-by SORT", "Field to sort results on (created, downloads, updated) Default: downloads") do |s|
38
40
  options[:sort] = s
39
41
  end
40
- o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address to login with") do |u|
41
- options[:username] = u
42
+ o.on("--[no-]auth", "Authenticate with Vagrant Cloud if required before searching") do |l|
43
+ options[:quiet] = !l
42
44
  end
43
45
  end
44
46
 
45
47
  # Parse the options
46
48
  argv = parse_options(opts)
47
49
  return if !argv
48
- if argv.length > 1
50
+ if argv.length != 1
49
51
  raise Vagrant::Errors::CLIInvalidUsage,
50
52
  help: opts.help.chomp
51
53
  end
52
54
 
53
- @client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username])
55
+ @client = client_login(@env, options.slice(:quiet))
54
56
  query = argv.first
55
57
 
56
58
  options[:limit] = 25 if !(options[:limit].to_i < 1) && !options[:limit]
57
59
 
58
- search(query, options, @client.token)
60
+ search(query, @client&.token, options)
59
61
  end
60
62
 
61
- def search(query, options, access_token)
62
- server_url = VagrantPlugins::CloudCommand::Util.api_server_url
63
- search = VagrantCloud::Search.new(access_token, server_url)
63
+ # Perform requested search and display results to user
64
+ #
65
+ # @param [String] query Search query string
66
+ # @param [Hash] options
67
+ # @option options [String] :provider Filter by provider
68
+ # @option options [String] :sort Field to sort results
69
+ # @option options [Integer] :limit Number of results to display
70
+ # @option options [Integer] :page Page of results to display
71
+ # @param [String] access_token User access token
72
+ # @return [Integer]
73
+ def search(query, access_token, options={})
74
+ account = VagrantCloud::Account.new(
75
+ custom_server: api_server_url,
76
+ access_token: access_token
77
+ )
78
+ params = {query: query}.merge(options.slice(:provider, :sort, :order, :limit, :page))
79
+ result = account.searcher.search(**params)
64
80
 
65
- begin
66
- search_results = search.search(query, options[:provider], options[:sort], options[:order], options[:limit], options[:page])
67
- if !search_results["boxes"].empty?
68
- VagrantPlugins::CloudCommand::Util.format_search_results(search_results["boxes"], options[:short], options[:json], @env)
69
- else
70
- @env.ui.warn(I18n.t("cloud_command.search.no_results", query: query))
71
- end
81
+ if result.boxes.empty?
82
+ @env.ui.warn(I18n.t("cloud_command.search.no_results", query: query))
72
83
  return 0
73
- rescue VagrantCloud::ClientError => e
74
- @env.ui.error(I18n.t("cloud_command.errors.search.fail"))
75
- @env.ui.error(e)
76
- return 1
77
84
  end
78
- return 1
85
+
86
+ format_search_results(result.boxes, options[:short], options[:json], @env)
87
+ 0
88
+ rescue VagrantCloud::Error => e
89
+ @env.ui.error(I18n.t("cloud_command.errors.search.fail"))
90
+ @env.ui.error(e.message)
91
+ 1
79
92
  end
80
93
  end
81
94
  end
@@ -1,205 +1,309 @@
1
1
  module VagrantPlugins
2
2
  module CloudCommand
3
- class Util
4
- class << self
5
- # @param [String] username - Vagrant Cloud username
6
- # @param [String] access_token - Vagrant Cloud Token used to authenticate
7
- # @param [String] vagrant_cloud_server - Vagrant Cloud server to make API request
8
- # @return [VagrantCloud::Account]
9
- def account(username, access_token, vagrant_cloud_server)
10
- if !defined?(@_account)
11
- @_account = VagrantCloud::Account.new(username, access_token, vagrant_cloud_server)
12
- end
13
- @_account
14
- end
15
-
16
- def api_server_url
17
- if Vagrant.server_url == Vagrant::DEFAULT_SERVER_URL
18
- return "#{Vagrant.server_url}/api/v1"
19
- else
20
- return Vagrant.server_url
21
- end
3
+ module Util
4
+ # @return [String] Vagrant Cloud server URL
5
+ def api_server_url
6
+ if Vagrant.server_url == Vagrant::DEFAULT_SERVER_URL
7
+ return "#{Vagrant.server_url}/api/v1"
8
+ else
9
+ return Vagrant.server_url
22
10
  end
11
+ end
23
12
 
24
- # @param [Vagrant::Environment] env
25
- # @param [Hash] options
26
- # @returns [VagrantPlugins::CloudCommand::Client]
27
- def client_login(env, options)
28
- if !defined?(@_client)
29
- @_client = Client.new(env)
30
- return @_client if @_client.logged_in?
13
+ # @param [Vagrant::Environment] env
14
+ # @param [Hash] options
15
+ # @option options [String] :login Username or email
16
+ # @option options [String] :description Description of login usage for token
17
+ # @option options [String] :code 2FA code for login
18
+ # @option options [Boolean] :quiet Do not prompt user
19
+ # @returns [VagrantPlugins::CloudCommand::Client, nil]
20
+ def client_login(env, options={})
21
+ return @_client if defined?(@_client)
22
+ @_client = Client.new(env)
23
+ return @_client if @_client.logged_in?
31
24
 
32
- # Let the user know what is going on.
33
- env.ui.output(I18n.t("cloud_command.command_header") + "\n")
25
+ # If directed to be quiet, do not continue and
26
+ # just return nil
27
+ return if options[:quiet]
34
28
 
35
- # If it is a private cloud installation, show that
36
- if Vagrant.server_url != Vagrant::DEFAULT_SERVER_URL
37
- env.ui.output("Vagrant Cloud URL: #{Vagrant.server_url}")
38
- end
29
+ # Let the user know what is going on.
30
+ env.ui.output(I18n.t("cloud_command.command_header") + "\n")
39
31
 
40
- options = {} if !options
41
- # Ask for the username
42
- if options[:login]
43
- @_client.username_or_email = options[:login]
44
- env.ui.output("Vagrant Cloud username or email: #{@_client.username_or_email}")
45
- else
46
- @_client.username_or_email = env.ui.ask("Vagrant Cloud username or email: ")
47
- end
32
+ # If it is a private cloud installation, show that
33
+ if Vagrant.server_url != Vagrant::DEFAULT_SERVER_URL
34
+ env.ui.output("Vagrant Cloud URL: #{Vagrant.server_url}")
35
+ end
48
36
 
49
- @_client.password = env.ui.ask("Password (will be hidden): ", echo: false)
37
+ options = {} if !options
38
+ # Ask for the username
39
+ if options[:login]
40
+ @_client.username_or_email = options[:login]
41
+ env.ui.output("Vagrant Cloud username or email: #{@_client.username_or_email}")
42
+ else
43
+ @_client.username_or_email = env.ui.ask("Vagrant Cloud username or email: ")
44
+ end
50
45
 
51
- description_default = "Vagrant login from #{Socket.gethostname}"
52
- if !options[:description]
53
- description = env.ui.ask("Token description (Defaults to #{description_default.inspect}): ")
54
- else
55
- description = options[:description]
56
- env.ui.output("Token description: #{description}")
57
- end
46
+ @_client.password = env.ui.ask("Password (will be hidden): ", echo: false)
58
47
 
59
- description = description_default if description.empty?
48
+ description_default = "Vagrant login from #{Socket.gethostname}"
49
+ if !options[:description]
50
+ description = env.ui.ask("Token description (Defaults to #{description_default.inspect}): ")
51
+ else
52
+ description = options[:description]
53
+ env.ui.output("Token description: #{description}")
54
+ end
60
55
 
61
- code = nil
56
+ description = description_default if description.empty?
62
57
 
63
- begin
64
- token = @_client.login(description: description, code: code)
65
- rescue Errors::TwoFactorRequired
66
- until code
67
- code = env.ui.ask("2FA code: ")
58
+ code = nil
68
59
 
69
- if @_client.two_factor_delivery_methods.include?(code.downcase)
70
- delivery_method, code = code, nil
71
- @_client.request_code delivery_method
72
- end
73
- end
60
+ begin
61
+ token = @_client.login(description: description, code: code)
62
+ rescue Errors::TwoFactorRequired
63
+ until code
64
+ code = env.ui.ask("2FA code: ")
74
65
 
75
- retry
66
+ if @_client.two_factor_delivery_methods.include?(code.downcase)
67
+ delivery_method, code = code, nil
68
+ @_client.request_code delivery_method
76
69
  end
77
-
78
- @_client.store_token(token)
79
- Vagrant::Util::CredentialScrubber.sensitive(token)
80
- env.ui.success(I18n.t("cloud_command.logged_in"))
81
- @_client
82
70
  end
83
- @_client
84
- end
85
71
 
86
- # ===================================================
87
- # Modified from https://stackoverflow.com/a/28685559
88
- # for printing arrays of hashes in formatted tables
89
- # ===================================================
90
-
91
- # @param [Vagrant::Environment] - env
92
- # @param [Hash] - column_labels - A hash of key values for table labels (i.e. {:col1=>"COL1", :col2=>"COL2"})
93
- # @param [Array] - results - An array of hashes
94
- # @param [Array] - to_jrust_keys - An array of column keys that should be right justified (default is left justified for all columns)
95
- def print_search_table(env, column_labels, results, to_rjust_keys)
96
- columns = column_labels.each_with_object({}) { |(col,label),h|
97
- h[col] = { label: label,
98
- width: [results.map { |g| g[col].size }.max, label.size].max
99
- }}
100
-
101
- write_header(env, columns)
102
- write_divider(env, columns)
103
- results.each { |h| write_line(env, columns, h,to_rjust_keys) }
104
- write_divider(env, columns)
72
+ retry
105
73
  end
106
74
 
107
- def write_header(env, columns)
108
- env.ui.info "| #{ columns.map { |_,g| g[:label].ljust(g[:width]) }.join(' | ') } |"
109
- end
75
+ @_client.store_token(token)
76
+ Vagrant::Util::CredentialScrubber.sensitive(token)
77
+ env.ui.success(I18n.t("cloud_command.logged_in"))
78
+ @_client
79
+ end
110
80
 
111
- def write_divider(env, columns)
112
- env.ui.info "+-#{ columns.map { |_,g| "-"*g[:width] }.join("-+-") }-+"
81
+ # Print search results from Vagrant Cloud to the console
82
+ #
83
+ # @param [Array<VagrantCloud::Box>] search_results Box search results from Vagrant Cloud
84
+ # @param [Boolean] short Print short summary
85
+ # @param [Boolean] json Print output in JSON format
86
+ # @param [Vagrant::Environment] env Current Vagrant environment
87
+ # @return [nil]
88
+ def format_search_results(search_results, short, json, env)
89
+ result = search_results.map do |b|
90
+ {
91
+ name: b.tag,
92
+ version: b.current_version.version,
93
+ downloads: format_downloads(b.downloads.to_s),
94
+ providers: b.current_version.providers.map(&:name).join(", ")
95
+ }
113
96
  end
114
97
 
115
- def write_line(env, columns,h,to_rjust_keys)
116
- str = h.keys.map { |k|
117
- if to_rjust_keys.include?(k)
118
- h[k].rjust(columns[k][:width])
119
- else
120
- h[k].ljust(columns[k][:width])
121
- end
122
- }.join(" | ")
123
- env.ui.info "| #{str} |"
98
+ if short
99
+ result.map { |b| env.ui.info(b[:name]) }
100
+ elsif json
101
+ env.ui.info(result.to_json)
102
+ else
103
+ column_labels = {}
104
+ columns = result.first.keys
105
+ columns.each do |c|
106
+ column_labels[c] = c.to_s.upcase
107
+ end
108
+ print_search_table(env, column_labels, result, [:downloads])
124
109
  end
110
+ nil
111
+ end
125
112
 
126
- # ===================================================
127
- # ===================================================
113
+ # Output box details result from Vagrant Cloud
114
+ #
115
+ # @param [VagrantCloud::Box, VagrantCloud::Box::Version] box Box or box version to display
116
+ # @param [Vagrant::Environment] env Current Vagrant environment
117
+ # @return [nil]
118
+ def format_box_results(box, env)
119
+ if box.is_a?(VagrantCloud::Box)
120
+ info = box_info(box)
121
+ elsif box.is_a?(VagrantCloud::Box::Provider)
122
+ info = version_info(box.version)
123
+ else
124
+ info = version_info(box)
125
+ end
128
126
 
129
- # Takes a "mostly" flat key=>value hash from Vagrant Cloud
130
- # and prints its results in a list
131
- #
132
- # @param [Hash] - results - A response hash from vagrant cloud
133
- # @param [Vagrant::Environment] - env
134
- def format_box_results(results, env)
135
- # TODO: remove other description fields? Maybe leave "short"?
136
- results.delete("description_html")
127
+ width = info.keys.map(&:size).max
128
+ info.each do |k, v|
129
+ whitespace = width - k.size
130
+ env.ui.info "#{k}: #{"".ljust(whitespace)} #{v}"
131
+ end
132
+ nil
133
+ end
137
134
 
138
- if results["current_version"]
139
- versions = results.delete("versions")
140
- results["providers"] = results["current_version"]["providers"]
135
+ # Load box and yield
136
+ #
137
+ # @param [VagrantCloud::Account] account Vagrant Cloud account
138
+ # @param [String] org Organization name
139
+ # @param [String] box Box name
140
+ # @yieldparam [VagrantCloud::Box] box Requested Vagrant Cloud box
141
+ # @yieldreturn [Integer]
142
+ # @return [Integer]
143
+ def with_box(account:, org:, box:)
144
+ org = account.organization(name: org)
145
+ b = org.boxes.detect { |b| b.name == box }
146
+ if !b
147
+ @env.ui.error(I18n.t("cloud_command.box.not_found",
148
+ org: org.username, box_name: box))
149
+ return 1
150
+ end
151
+ yield b
152
+ end
141
153
 
142
- results["old_versions"] = versions.map{ |v| v["version"] }[1..5].join(", ") + "..."
154
+ # Load box version and yield
155
+ #
156
+ # @param [VagrantCloud::Account] account Vagrant Cloud account
157
+ # @param [String] org Organization name
158
+ # @param [String] box Box name
159
+ # @param [String] version Box version
160
+ # @yieldparam [VagrantCloud::Box::Version] version Requested Vagrant Cloud box version
161
+ # @yieldreturn [Integer]
162
+ # @return [Integer]
163
+ def with_version(account:, org:, box:, version:)
164
+ with_box(account: account, org: org, box: box) do |b|
165
+ v = b.versions.detect { |v| v.version == version }
166
+ if !v
167
+ @env.ui.error(I18n.t("cloud_command.version.not_found",
168
+ box_name: box, org: org, version: version))
169
+ return 1
143
170
  end
171
+ yield v
172
+ end
173
+ end
144
174
 
175
+ # Load box version and yield
176
+ #
177
+ # @param [VagrantCloud::Account] account Vagrant Cloud account
178
+ # @param [String] org Organization name
179
+ # @param [String] box Box name
180
+ # @param [String] version Box version
181
+ # @param [String] provider Box version provider name
182
+ # @yieldparam [VagrantCloud::Box::Provider] provider Requested Vagrant Cloud box version provider
183
+ # @yieldreturn [Integer]
184
+ # @return [Integer]
185
+ def with_provider(account:, org:, box:, version:, provider:)
186
+ with_version(account: account, org: org, box: box, version: version) do |v|
187
+ p = v.providers.detect { |p| p.name == provider }
188
+ if !p
189
+ @env.ui.error(I18n.t("cloud_command.provider.not_found",
190
+ org: org, box_name: box, version: version, provider_name: provider))
191
+ return 1
192
+ end
193
+ yield p
194
+ end
195
+ end
145
196
 
146
- width = results.keys.map{|k| k.size}.max
147
- results.each do |k,v|
148
- if k == "versions"
149
- v = v.map{ |ver| ver["version"] }.join(", ")
150
- elsif k == "current_version"
151
- v = v["version"]
152
- elsif k == "providers"
153
- v = v.map{ |p| p["name"] }.join(", ")
154
- elsif k == "downloads"
155
- v = format_downloads(v.to_s)
156
- end
197
+ protected
157
198
 
158
- whitespace = width-k.size
159
- env.ui.info "#{k}:" + "".ljust(whitespace) + " #{v}"
199
+ # Extract box information for display
200
+ #
201
+ # @param [VagrantCloud::Box] box Box for extracting information
202
+ # @return [Hash<String,String>]
203
+ def box_info(box)
204
+ Hash.new.tap do |i|
205
+ i["Box"] = box.tag
206
+ i["Description"] = box.description
207
+ i["Private"] = box.private ? "yes" : "no"
208
+ i["Created"] = box.created_at
209
+ i["Updated"] = box.updated_at
210
+ if !box.current_version.nil?
211
+ i["Current Version"] = box.current_version.version
212
+ else
213
+ i["Current Version"] = "N/A"
214
+ end
215
+ i["Versions"] = box.versions.slice(0, 5).map(&:version).join(", ")
216
+ if box.versions.size > 5
217
+ i["Versions"] += " ..."
160
218
  end
219
+ i["Downloads"] = format_downloads(box.downloads)
220
+ end
221
+ end
222
+
223
+ # Extract version information for display
224
+ #
225
+ # @param [VagrantCloud::Box::Version] version Box version for extracting information
226
+ # @return [Hash<String,String>]
227
+ def version_info(version)
228
+ Hash.new.tap do |i|
229
+ i["Box"] = version.box.tag
230
+ i["Version"] = version.version
231
+ i["Description"] = version.description
232
+ i["Status"] = version.status
233
+ i["Providers"] = version.providers.map(&:name).sort.join(", ")
234
+ i["Created"] = version.created_at
235
+ i["Updated"] = version.updated_at
161
236
  end
237
+ end
162
238
 
163
- # Converts a string of numbers into a formatted number
164
- #
165
- # 1234 -> 1,234
166
- #
167
- # @param [String] - download_string
168
- def format_downloads(download_string)
169
- return download_string.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
239
+ # Print table results from search request
240
+ #
241
+ # @param [Vagrant::Environment] env Current Vagrant environment
242
+ # @param [Hash] column_labels A hash of key/value pairs for table labels (i.e. {col1: "COL1"})
243
+ # @param [Array] results An array of hashes representing search resuls
244
+ # @param [Array] to_jrust_keys - List of columns keys to right justify (left justify is defualt)
245
+ # @return [nil]
246
+ # @note Modified from https://stackoverflow.com/a/28685559
247
+ def print_search_table(env, column_labels, results, to_rjust_keys)
248
+ columns = column_labels.each_with_object({}) do |(col,label),h|
249
+ h[col] = {
250
+ label: label,
251
+ width: [results.map { |g| g[col].size }.max, label.size].max
252
+ }
170
253
  end
171
254
 
255
+ write_header(env, columns)
256
+ write_divider(env, columns)
257
+ results.each { |h| write_line(env, columns, h, to_rjust_keys) }
258
+ write_divider(env, columns)
259
+ end
172
260
 
173
- # @param [Array] search_results - Box search results from Vagrant Cloud
174
- # @param [String,nil] short - determines if short version will be printed
175
- # @param [String,nil] json - determines if json version will be printed
176
- # @param [Vagrant::Environment] - env
177
- def format_search_results(search_results, short, json, env)
178
- result = []
179
- search_results.each do |b|
180
- box = {}
181
- box = {
182
- name: b["tag"],
183
- version: b["current_version"]["version"],
184
- downloads: format_downloads(b["downloads"].to_s),
185
- providers: b["current_version"]["providers"].map{ |p| p["name"] }.join(",")
186
- }
187
- result << box
188
- end
261
+ # Write the header for a table
262
+ #
263
+ # @param [Vagrant::Environment] env Current Vagrant environment
264
+ # @param [Array<Hash>] columns List of columns in Hash format with `:label` and `:width` keys
265
+ # @return [nil]
266
+ def write_header(env, columns)
267
+ env.ui.info "| #{ columns.map { |_,g| g[:label].ljust(g[:width]) }.join(' | ') } |"
268
+ nil
269
+ end
189
270
 
190
- if short
191
- result.map {|b| env.ui.info(b[:name])}
192
- elsif json
193
- env.ui.info(result.to_json)
271
+ # Write a row divider for a table
272
+ #
273
+ # @param [Vagrant::Environment] env Current Vagrant environment
274
+ # @param [Array<Hash>] columns List of columns in Hash format with `:label` and `:width` keys
275
+ # @return [nil]
276
+ def write_divider(env, columns)
277
+ env.ui.info "+-#{ columns.map { |_,g| "-"*g[:width] }.join("-+-") }-+"
278
+ nil
279
+ end
280
+
281
+ # Write a line of content for a table
282
+ #
283
+ # @param [Vagrant::Environment] env Current Vagrant environment
284
+ # @param [Array<Hash>] columns List of columns in Hash format with `:label` and `:width` keys
285
+ # @param [Hash] h Values to print in row
286
+ # @param [Array<String>] to_rjust_keys List of columns to right justify
287
+ # @return [nil]
288
+ def write_line(env, columns, h, to_rjust_keys)
289
+ str = h.keys.map { |k|
290
+ if to_rjust_keys.include?(k)
291
+ h[k].rjust(columns[k][:width])
194
292
  else
195
- column_labels = {}
196
- columns = result.first.keys
197
- columns.each do |c|
198
- column_labels[c] = c.to_s.upcase
199
- end
200
- print_search_table(env, column_labels, result, [:downloads])
293
+ h[k].ljust(columns[k][:width])
201
294
  end
202
- end
295
+ }.join(" | ")
296
+ env.ui.info "| #{str} |"
297
+ nil
298
+ end
299
+
300
+ # Converts a string of numbers into a formatted number
301
+ #
302
+ # 1234 -> 1,234
303
+ #
304
+ # @param [String] number Numer to format
305
+ def format_downloads(number)
306
+ number.to_s.chars.reverse.each_slice(3).map(&:join).join(",").reverse
203
307
  end
204
308
  end
205
309
  end