From d7b74ca8b9212686b6b8a35617f869b1dfd855a5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 25 Nov 2013 21:57:20 -0800 Subject: [PATCH] core: config.vm.box_url can be array of urls [GH-1958] --- CHANGELOG.md | 1 + lib/vagrant/action/builtin/box_add.rb | 129 +++++++++++------- plugins/kernel_v2/config/vm.rb | 5 + templates/locales/en.yml | 4 +- .../v2/vagrantfile/machine_settings.html.md | 4 + 5 files changed, 95 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f540dc338..d35ff0f2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ IMPROVEMENTS: specified for `vagrant box list` [GH-2327] - core: Multiple SSH keys can be specified with `config.ssh.private_key_path` [GH-907] + - core: `config.vm.box_url` can be an array of URLs. [GH-1958] - commands/box/add: Can now specify a client cert when downloading a box. [GH-1889] - commands/init: Add `--output` option for specifing output path, or diff --git a/lib/vagrant/action/builtin/box_add.rb b/lib/vagrant/action/builtin/box_add.rb index 6f5754312..c0906e7f6 100644 --- a/lib/vagrant/action/builtin/box_add.rb +++ b/lib/vagrant/action/builtin/box_add.rb @@ -17,57 +17,33 @@ module Vagrant def call(env) @download_interrupted = false - @temp_path = env[:tmp_path].join("box" + Digest::SHA1.hexdigest(env[:box_url])) - @logger.info("Downloading box to: #{@temp_path}") - url = env[:box_url] - if File.file?(url) || url !~ /^[a-z0-9]+:.*$/i - @logger.info("URL is a file or protocol not found and assuming file.") - file_path = File.expand_path(url) - file_path = Util::Platform.cygwin_windows_path(file_path) - url = "file:#{file_path}" - end - - downloader_options = {} - downloader_options[:continue] = true - downloader_options[:insecure] = env[:box_download_insecure] - downloader_options[:ui] = env[:ui] - downloader_options[:client_cert] = env[:box_client_cert] - - # If the temporary path exists, verify it is not too old. If its - # too old, delete it first because the data may have changed. - if @temp_path.file? - delete = false - if env[:box_clean] - @logger.info("Cleaning existing temp box file.") - delete = true - elsif @temp_path.mtime.to_i < (Time.now.to_i - 6 * 60 * 60) - @logger.info("Existing temp file is too old. Removing.") - delete = true + # Go through each URL and attempt to download it + download_error = nil + download_url = nil + urls = env[:box_url] + urls = [env[:box_url]] if !urls.is_a?(Array) + urls.each do |url| + begin + @temp_path = download_box_url(url, env) + download_error = nil + download_url = url + rescue Errors::DownloaderError => e + env[:ui].error(I18n.t( + "vagrant.actions.box.download.download_failed")) + download_error = e end - @temp_path.unlink if delete + # If we were interrupted during this download, then just return + # at this point, we don't need to try anymore. + if @download_interrupted + @logger.warn("Download interrupted, not trying any more box URLs.") + return + end end - # Download the box to a temporary path. We store the temporary - # path as an instance variable so that the `#recover` method can - # access it. - env[:ui].info(I18n.t("vagrant.actions.box.download.downloading")) - - if @temp_path.file? - env[:ui].info(I18n.t("vagrant.actions.box.download.resuming")) - end - - begin - downloader = Util::Downloader.new(url, @temp_path, downloader_options) - downloader.download! - rescue Errors::DownloaderInterrupted - # The downloader was interrupted, so just return, because that - # means we were interrupted as well. - @download_interrupted = true - env[:ui].info(I18n.t("vagrant.actions.box.download.interrupted")) - return - end + # If all the URLs failed, then raise an exception + raise download_error if download_error box_formats = env[:box_provider] if box_formats @@ -103,7 +79,7 @@ module Vagrant I18n.t("vagrant.actions.box.add.added", name: box_added.name, provider: box_added.provider)) # Persists URL used on download and the time it was added - write_extra_info(box_added, url) + write_extra_info(box_added, download_url) # Passes on the newly added box to the rest of the middleware chain env[:box_added] = box_added @@ -118,6 +94,65 @@ module Vagrant end end + def download_box_url(url, env) + temp_path = env[:tmp_path].join("box" + Digest::SHA1.hexdigest(url)) + @logger.info("Downloading box: #{url} => #{temp_path}") + + if File.file?(url) || url !~ /^[a-z0-9]+:.*$/i + @logger.info("URL is a file or protocol not found and assuming file.") + file_path = File.expand_path(url) + file_path = Util::Platform.cygwin_windows_path(file_path) + url = "file:#{file_path}" + end + + downloader_options = {} + downloader_options[:continue] = true + downloader_options[:insecure] = env[:box_download_insecure] + downloader_options[:ui] = env[:ui] + downloader_options[:client_cert] = env[:box_client_cert] + + # If the temporary path exists, verify it is not too old. If its + # too old, delete it first because the data may have changed. + if temp_path.file? + delete = false + if env[:box_clean] + @logger.info("Cleaning existing temp box file.") + delete = true + elsif temp_path.mtime.to_i < (Time.now.to_i - 6 * 60 * 60) + @logger.info("Existing temp file is too old. Removing.") + delete = true + end + + temp_path.unlink if delete + end + + # Download the box to a temporary path. We store the temporary + # path as an instance variable so that the `#recover` method can + # access it. + env[:ui].info(I18n.t( + "vagrant.actions.box.download.downloading", + url: url)) + if temp_path.file? + env[:ui].info(I18n.t("vagrant.actions.box.download.resuming")) + end + + begin + downloader = Util::Downloader.new(url, temp_path, downloader_options) + downloader.download! + rescue Errors::DownloaderInterrupted + # The downloader was interrupted, so just return, because that + # means we were interrupted as well. + @download_interrupted = true + env[:ui].info(I18n.t("vagrant.actions.box.download.interrupted")) + rescue Errors::DownloaderError + # The download failed for some reason, clean out the temp path + temp_path.unlink if temp_path.file? + raise + end + + temp_path + end + def write_extra_info(box_added, url) info = {'url' => url, 'downloaded_at' => Time.now.utc} box_added.directory.join('info.json').open("w+") do |f| diff --git a/plugins/kernel_v2/config/vm.rb b/plugins/kernel_v2/config/vm.rb index 1bb9d9371..bd2a1f60c 100644 --- a/plugins/kernel_v2/config/vm.rb +++ b/plugins/kernel_v2/config/vm.rb @@ -261,6 +261,11 @@ module VagrantPlugins @hostname = nil if @hostname == UNSET_VALUE @hostname = @hostname.to_s if @hostname + # Make sure the box URL is an array if it is set + if @box_url && !@box_url.is_a?(Array) + @box_url = [@box_url] + end + # Set the guest properly @guest = @guest.to_sym if @guest diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 21a865b41..c054f19a0 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1120,7 +1120,9 @@ en: destroying: "Deleting box '%{name}'..." download: cleaning: "Cleaning up downloaded box..." - downloading: "Downloading or copying the box..." + downloading: "Downloading box from URL: %{url}" + download_failed: |- + Download failed. Will try another box URL if there is one. interrupted: "Box download was interrupted. Exiting." resuming: "Box download is resuming from prior download progress" verify: diff --git a/website/docs/source/v2/vagrantfile/machine_settings.html.md b/website/docs/source/v2/vagrantfile/machine_settings.html.md index 2177eacfd..b32b0b1de 100644 --- a/website/docs/source/v2/vagrantfile/machine_settings.html.md +++ b/website/docs/source/v2/vagrantfile/machine_settings.html.md @@ -39,6 +39,10 @@ URL, then SSL certs will be verified. If the box is not installed on the system, it will be retrieved from this URL when `vagrant up` is run. +This can also be an array of multiple URLs. The URLs will be tried in +order. Note that any client certificates, insecure download settings, and +so on will apply to all URLs in this list. +
`config.vm.graceful_halt_timeout` - The time in seconds that Vagrant will