vagrant-unbundled 2.2.10.0 → 2.2.14.0

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