diff --git a/bin/vagrant b/bin/vagrant index 7372a726d..0e6abdcef 100755 --- a/bin/vagrant +++ b/bin/vagrant @@ -89,6 +89,7 @@ begin require 'vagrant/bundler' require 'vagrant/cli' require 'vagrant/util/platform' + require 'vagrant/util/experimental' # Schedule the cleanup of things at_exit(&Vagrant::Bundler.instance.method(:deinit)) @@ -159,6 +160,23 @@ begin env.ui.warn(I18n.t("vagrant.general.not_in_installer") + "\n", prefix: false) end + # Acceptable experimental flag values include: + # + # Unset - Disables experimental features + # 0 - Disables experimental features + # 1 - Enables all features + # String - Enables one or more features, separated by commas + if Vagrant::Util::Experimental.enabled? + experimental = Vagrant::Util::Experimental.features_requested + ui = Vagrant::UI::Prefixed.new(env.ui, "vagrant") + logger.debug("Experimental flag is enabled") + if Vagrant::Util::Experimental.global_enabled? + ui.warn(I18n.t("vagrant.general.experimental.all"), bold: true, prefix: true, channel: :error) + else + ui.warn(I18n.t("vagrant.general.experimental.features", features: experimental.join(", ")), bold: true, prefix: true, channel: :error) + end + end + begin # Execute the CLI interface, and exit with the proper error code exit_status = env.cli(argv) diff --git a/lib/vagrant/util/experimental.rb b/lib/vagrant/util/experimental.rb new file mode 100644 index 000000000..8888a7224 --- /dev/null +++ b/lib/vagrant/util/experimental.rb @@ -0,0 +1,76 @@ +module Vagrant + module Util + class Experimental + class << self + # A method for determining if the experimental flag has been enabled with + # any features + # + # @return [Boolean] + def enabled? + if !defined?(@_experimental) + experimental = features_requested + if experimental.size >= 1 && experimental.first != "0" + @_experimental = true + else + @_experimental = false + end + end + @_experimental + end + + # A method for determining if all experimental features have been enabled + # by either a global enabled value "1" or all features explicitly enabled. + # + # @return [Boolean] + def global_enabled? + if !defined?(@_global_enabled) + experimental = features_requested + if experimental.size == 1 && experimental.first == "1" + @_global_enabled = true + else + @_global_enabled = false + end + end + @_global_enabled + end + + # A method for Vagrant internals to determine if a given feature + # has been abled by the user, is a valid feature flag and can be used. + # + # @param [String] feature + # @return [Boolean] - A hash containing the original array and if it is valid + def feature_enabled?(feature) + experimental = features_requested + feature = feature.to_s + + return global_enabled? || experimental.include?(feature) + end + + # Returns the features requested for the experimental flag + # + # @return [Array] - Returns an array of requested experimental features + def features_requested + if !defined?(@_requested_features) + @_requested_features = ENV["VAGRANT_EXPERIMENTAL"].to_s.downcase.split(',') + end + @_requested_features + end + + # A function to guard experimental blocks of code from being executed + # + # @param [Array] features - Array of features to guard a method with + # @param [Block] block - Block of ruby code to be guarded against + def guard_with(*features, &block) + yield if block_given? && features.any? {|f| feature_enabled?(f)} + end + + # @private + # Reset the cached values for platform. This is not considered a public + # API and should only be used for testing. + def reset! + instance_variables.each(&method(:remove_instance_variable)) + end + end + end + end +end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index e6967d2a0..3890182b6 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -393,6 +393,18 @@ en: shown below. %{output} + experimental: + all: |- + You have enabled the experimental flag with all features enabled. + Please use with caution, as some of the features may not be fully + functional yet. + features: |- + You have requested to enabled the experimental flag with the following features: + + Features: %{features} + + Please use with caution, as some of the features may not be fully + functional yet. not_in_installer: |- You appear to be running Vagrant outside of the official installers. Note that the installers are what ensure that Vagrant has all required diff --git a/test/unit/vagrant/util/experimental_test.rb b/test/unit/vagrant/util/experimental_test.rb new file mode 100644 index 000000000..d4a03c4ad --- /dev/null +++ b/test/unit/vagrant/util/experimental_test.rb @@ -0,0 +1,109 @@ +require File.expand_path("../../../base", __FILE__) + +require "vagrant/util/experimental" + +describe Vagrant::Util::Experimental do + include_context "unit" + before(:each) { described_class.reset! } + subject { described_class } + + describe "#enabled?" do + it "returns true if enabled with '1'" do + allow(ENV).to receive(:[]).with("VAGRANT_EXPERIMENTAL").and_return("1") + expect(subject.enabled?).to eq(true) + end + + it "returns true if enabled with a list of features" do + allow(ENV).to receive(:[]).with("VAGRANT_EXPERIMENTAL").and_return("list,of,features") + expect(subject.enabled?).to eq(true) + end + + it "returns false if disabled" do + allow(ENV).to receive(:[]).with("VAGRANT_EXPERIMENTAL").and_return("0") + expect(subject.enabled?).to eq(false) + end + + it "returns false if not set" do + allow(ENV).to receive(:[]).with("VAGRANT_EXPERIMENTAL").and_return(nil) + expect(subject.enabled?).to eq(false) + end + end + + describe "#global_enabled?" do + it "returns true if enabled with '1'" do + allow(ENV).to receive(:[]).with("VAGRANT_EXPERIMENTAL").and_return("1") + expect(subject.global_enabled?).to eq(true) + end + + it "returns false if enabled with a partial list of features" do + allow(ENV).to receive(:[]).with("VAGRANT_EXPERIMENTAL").and_return("list,of,features") + expect(subject.global_enabled?).to eq(false) + end + + it "returns false if disabled" do + allow(ENV).to receive(:[]).with("VAGRANT_EXPERIMENTAL").and_return("0") + expect(subject.global_enabled?).to eq(false) + end + + it "returns false if not set" do + allow(ENV).to receive(:[]).with("VAGRANT_EXPERIMENTAL").and_return(nil) + expect(subject.global_enabled?).to eq(false) + end + end + + describe "#feature_enabled?" do + it "returns true if flag set to 1" do + allow(ENV).to receive(:[]).with("VAGRANT_EXPERIMENTAL").and_return("1") + expect(subject.feature_enabled?("anything")).to eq(true) + end + + it "returns true if flag contains feature requested" do + allow(ENV).to receive(:[]).with("VAGRANT_EXPERIMENTAL").and_return("secret_feature") + expect(subject.feature_enabled?("secret_feature")).to eq(true) + end + + it "returns true if flag contains feature requested and the request is a symbol" do + allow(ENV).to receive(:[]).with("VAGRANT_EXPERIMENTAL").and_return("secret_feature") + expect(subject.feature_enabled?(:secret_feature)).to eq(true) + end + + it "returns true if flag contains feature requested with other features 'enabled'" do + allow(ENV).to receive(:[]).with("VAGRANT_EXPERIMENTAL").and_return("secret_feature,other_secret") + expect(subject.feature_enabled?("secret_feature")).to eq(true) + end + + it "returns false if flag is set but does not contain feature requested" do + allow(ENV).to receive(:[]).with("VAGRANT_EXPERIMENTAL").and_return("fake_feature") + expect(subject.feature_enabled?("secret_feature")).to eq(false) + end + + it "returns false if flag set to 0" do + allow(ENV).to receive(:[]).with("VAGRANT_EXPERIMENTAL").and_return("0") + expect(subject.feature_enabled?("anything")).to eq(false) + end + + it "returns false if flag is not set" do + allow(ENV).to receive(:[]).with("VAGRANT_EXPERIMENTAL").and_return(nil) + expect(subject.feature_enabled?("anything")).to eq(false) + end + end + + describe "#features_requested" do + it "returns an array of requested features" do + allow(ENV).to receive(:[]).with("VAGRANT_EXPERIMENTAL").and_return("secret_feature,other_secret") + expect(subject.features_requested).to eq(["secret_feature","other_secret"]) + end + end + + describe "#guard_with" do + it "does not execute the block if the feature is not requested" do + allow(ENV).to receive(:[]).with("VAGRANT_EXPERIMENTAL").and_return(nil) + expect{|b| subject.guard_with("secret_feature", &b) }.not_to yield_control + end + + it "executes the block if the feature is valid and requested" do + allow(ENV).to receive(:[]).with("VAGRANT_EXPERIMENTAL").and_return("secret_feature,other_secret") + expect{|b| subject.guard_with("secret_feature", &b) }.to yield_control + end + end +end