Fixes #10224: Allow validation of config while ignoring provider

This commit adds a new flag to the `vagrant validate` command which
allows users to completely ignore the provider block of a config file.
This is useful for when you are running `vagrant validate` in CI and
don't want to install a valid provider to check the syntax of your
Vagratnfile. When the flag is invoked, a warning will be displayed
saying that the provider block will be ignored and not validated.
This commit is contained in:
Brian Cain 2018-10-30 12:35:46 -07:00
parent 716a5f1501
commit 6051f3598e
No known key found for this signature in database
GPG Key ID: 9FC4639B2E4510A0
8 changed files with 170 additions and 47 deletions

View File

@ -12,7 +12,7 @@ module Vagrant
def call(env) def call(env)
if !env.key?(:config_validate) || env[:config_validate] if !env.key?(:config_validate) || env[:config_validate]
errors = env[:machine].config.validate(env[:machine]) errors = env[:machine].config.validate(env[:machine], env[:ignore_provider])
if errors && !errors.empty? if errors && !errors.empty?
raise Errors::ConfigInvalid, raise Errors::ConfigInvalid,

View File

@ -60,14 +60,18 @@ module Vagrant
# #
# @param [Environment] env # @param [Environment] env
# @return [Hash] # @return [Hash]
def validate(machine) def validate(machine, ignore_provider=nil)
# Go through each of the configuration keys and validate # Go through each of the configuration keys and validate
errors = {} errors = {}
@keys.each do |_key, instance| @keys.each do |_key, instance|
if instance.respond_to?(:validate) if instance.respond_to?(:validate)
# Validate this single item, and if we have errors then # Validate this single item, and if we have errors then
# we merge them into our total errors list. # we merge them into our total errors list.
result = instance.validate(machine) if _key == :vm
result = instance.validate(machine, ignore_provider)
else
result = instance.validate(machine)
end
if result && !result.empty? if result && !result.empty?
errors = Util.merge_errors(errors, result) errors = Util.merge_errors(errors, result)
end end

View File

@ -8,17 +8,32 @@ module VagrantPlugins
end end
def execute def execute
options = {}
opts = OptionParser.new do |o| opts = OptionParser.new do |o|
o.banner = "Usage: vagrant validate" o.banner = "Usage: vagrant validate [options]"
o.separator ""
o.separator "Validates a Vagrantfile config"
o.separator ""
o.separator "Options:"
o.separator ""
o.on("-p", "--ignore-provider", "Ignores provider config options") do |p|
options[:ignore_provider] = p
end
end end
# Parse the options # Parse the options
argv = parse_options(opts) argv = parse_options(opts)
return if !argv return if !argv
action_env = {}
if options[:ignore_provider]
action_env[:ignore_provider] = true
end
# Validate the configuration of all machines # Validate the configuration of all machines
with_target_vms() do |machine| with_target_vms() do |machine|
machine.action_raw(:config_validate, Vagrant::Action::Builtin::ConfigValidate) machine.action_raw(:config_validate, Vagrant::Action::Builtin::ConfigValidate, action_env)
end end
@env.ui.info(I18n.t("vagrant.commands.validate.success")) @env.ui.info(I18n.t("vagrant.commands.validate.success"))

View File

@ -582,7 +582,7 @@ module VagrantPlugins
@__synced_folders @__synced_folders
end end
def validate(machine) def validate(machine, ignore_provider=nil)
errors = _detected_errors errors = _detected_errors
if !box && !clone && !machine.provider_options[:box_optional] if !box && !clone && !machine.provider_options[:box_optional]
@ -737,9 +737,13 @@ module VagrantPlugins
# Validate only the _active_ provider # Validate only the _active_ provider
if machine.provider_config if machine.provider_config
provider_errors = machine.provider_config.validate(machine) if !ignore_provider
if provider_errors provider_errors = machine.provider_config.validate(machine)
errors = Vagrant::Config::V2::Util.merge_errors(errors, provider_errors) if provider_errors
errors = Vagrant::Config::V2::Util.merge_errors(errors, provider_errors)
end
else
machine.ui.warn(I18n.t("vagrant.config.vm.ignore_provider_config"))
end end
end end

View File

@ -1845,6 +1845,8 @@ en:
hostname_invalid_characters: |- hostname_invalid_characters: |-
The hostname set for the VM should only contain letters, numbers, The hostname set for the VM should only contain letters, numbers,
hyphens or dots. It cannot start with a hyphen or dot. hyphens or dots. It cannot start with a hyphen or dot.
ignore_provider_config: |-
Ignoring provider config for validation...
name_invalid: |- name_invalid: |-
The sub-VM name '%{name}' is invalid. Please don't use special characters. The sub-VM name '%{name}' is invalid. Please don't use special characters.
network_ip_ends_in_one: |- network_ip_ends_in_one: |-

View File

@ -5,13 +5,15 @@ describe VagrantPlugins::CommandValidate::Command do
include_context "unit" include_context "unit"
include_context "command plugin helpers" include_context "command plugin helpers"
let(:vagrantfile_content){ "" }
let(:iso_env) do let(:iso_env) do
isolated_environment env = isolated_environment
env.vagrantfile(vagrantfile_content)
env.create_vagrant_env
end end
let(:env) do let(:action_runner) { double("action_runner") }
iso_env.create_vagrant_env let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }
end
let(:argv) { [] } let(:argv) { [] }
@ -20,37 +22,49 @@ describe VagrantPlugins::CommandValidate::Command do
I18n.reload! I18n.reload!
end end
subject { described_class.new(argv, env) } subject { described_class.new(argv, iso_env) }
describe "#execute" do describe "#execute" do
it "validates correct Vagrantfile" do context "validating configs" do
iso_env.vagrantfile(<<-EOH) let(:vagrantfile_content) do
Vagrant.configure("2") do |config| <<-VF
config.vm.box = "hashicorp/precise64" Vagrant.configure("2") do |config|
config.vm.box = "hashicorp/precise64"
end
VF
end
it "validates correct Vagrantfile" do
expect(machine).to receive(:action_raw) do |name, action, env|
expect(name).to eq(:config_validate)
expect(action).to eq(Vagrant::Action::Builtin::ConfigValidate)
expect(env).to eq({})
end end
EOH expect(iso_env.ui).to receive(:info).with(any_args) { |message, _|
expect(message).to include("Vagrantfile validated successfully.")
}
expect(env.ui).to receive(:info).with(any_args) { |message, _| expect(subject.execute).to eq(0)
expect(message).to include("Vagrantfile validated successfully.") end
}
expect(subject.execute).to eq(0)
end end
it "validates the configuration" do context "invalid configs" do
iso_env.vagrantfile <<-EOH let(:vagrantfile_content) do
<<-VF
Vagrant.configure("2") do |config| Vagrant.configure("2") do |config|
config.vm.bix = "hashicorp/precise64" config.vm.bix = "hashicorp/precise64"
end end
EOH VF
end
expect { subject.execute }.to raise_error(Vagrant::Errors::ConfigInvalid) { |err| it "validates the configuration" do
expect(err.message).to include("The following settings shouldn't exist: bix") expect { subject.execute }.to raise_error(Vagrant::Errors::ConfigInvalid) { |err|
} expect(err.message).to include("The following settings shouldn't exist: bix")
}
end
end end
it "validates correct Vagrantfile of all vms" do context "valid configs for multiple vms" do
iso_env.vagrantfile <<-EOH let(:vagrantfile_content) do
<<-VF
Vagrant.configure("2") do |config| Vagrant.configure("2") do |config|
config.vm.box = "hashicorp/precise64" config.vm.box = "hashicorp/precise64"
@ -62,17 +76,25 @@ describe VagrantPlugins::CommandValidate::Command do
vm.vm.provider :virtualbox vm.vm.provider :virtualbox
end end
end end
EOH VF
end
it "validates correct Vagrantfile of all vms" do
expect(machine).to receive(:action_raw) do |name, action, env|
expect(name).to eq(:config_validate)
expect(action).to eq(Vagrant::Action::Builtin::ConfigValidate)
expect(env).to eq({})
end
expect(iso_env.ui).to receive(:info).with(any_args) { |message, _|
expect(message).to include("Vagrantfile validated successfully.")
}
expect(env.ui).to receive(:info).with(any_args) { |message, _| expect(subject.execute).to eq(0)
expect(message).to include("Vagrantfile validated successfully.") end
}
expect(subject.execute).to eq(0)
end end
it "validates the configuration of all vms" do context "an invalid config for some vms" do
iso_env.vagrantfile <<-EOH let(:vagrantfile_content) do
<<-VF
Vagrant.configure("2") do |config| Vagrant.configure("2") do |config|
config.vm.box = "hashicorp/precise64" config.vm.box = "hashicorp/precise64"
@ -84,15 +106,59 @@ describe VagrantPlugins::CommandValidate::Command do
vm.vm.not_provider :virtualbox vm.vm.not_provider :virtualbox
end end
end end
EOH VF
end
it "validates the configuration of all vms" do
expect(machine).to receive(:action_raw) do |name, action, env|
expect(name).to eq(:config_validate)
expect(action).to eq(Vagrant::Action::Builtin::ConfigValidate)
expect(env).to eq({})
end
expect { subject.execute }.to raise_error(Vagrant::Errors::ConfigInvalid) { |err| expect { subject.execute }.to raise_error(Vagrant::Errors::ConfigInvalid) { |err|
expect(err.message).to include("The following settings shouldn't exist: not_provider") expect(err.message).to include("The following settings shouldn't exist: not_provider")
} }
end
end end
it "throws an exception if there's no Vagrantfile" do context "with the ignore provider flag" do
expect { subject.execute }.to raise_error(Vagrant::Errors::NoEnvironmentError) let(:argv) { ["--ignore-provider"]}
let(:vagrantfile_content) do
<<-VF
Vagrant.configure("2") do |config|
config.vm.box = "hashicorp/precise64"
config.vm.define "test" do |vm|
vm.vm.hostname = "test"
vm.vm.provider :virtualbox do |v|
v.not_a_real_option = true
end
end
end
VF
end
it "ignores provider specific configurations with the flag" do
expect(iso_env.ui).to receive(:info).with(any_args) { |message, _|
expect(message).to include("Vagrantfile validated successfully.")
}
expect(machine).to receive(:action_raw) do |name, action, env|
expect(name).to eq(:config_validate)
expect(action).to eq(Vagrant::Action::Builtin::ConfigValidate)
expect(env).to eq({:ignore_provider=>true})
end
expect(subject.execute).to eq(0)
end
end
context "no vagrantfile" do
let(:vagrantfile_content){ "" }
let(:env) { isolated_environment.create_vagrant_env }
subject { described_class.new(argv, env) }
it "throws an exception if there's no Vagrantfile" do
expect { subject.execute }.to raise_error(Vagrant::Errors::NoEnvironmentError)
end
end end
end end
end end

View File

@ -368,6 +368,20 @@ describe VagrantPlugins::Kernel_V2::VMConfig do
expect { subject.finalize! }. expect { subject.finalize! }.
to raise_error(Vagrant::Errors::VagrantfileLoadError) to raise_error(Vagrant::Errors::VagrantfileLoadError)
end end
it "ignores providers entirely if flag is provided" do
subject.provider "virtualbox" do |vb|
vb.nope = true
end
subject.provider "virtualbox" do |vb|
vb.not_real = "foo"
end
subject.finalize!
errors = subject.validate(machine, true)
expect(errors).to eq({"vm"=>[]})
end
end end
describe "#provision" do describe "#provision" do

View File

@ -91,6 +91,24 @@ describe Vagrant::Config::V2::Root do
expect(instance.validate(env)).to eq(errors) expect(instance.validate(env)).to eq(errors)
end end
context "with vms and ignoring provider validations" do
let(:instance) do
map = { vm: Object, bar: Object }
described_class.new(map)
end
it "should pass along the ignore_provider flag for ignoring validations" do
errors = { "vm" => ["errors!"] }
env = { "errors" => errors }
vm = instance.vm
def vm.validate(env, param)
env["errors"]
end
expect(instance.validate({}, true)).to eq({})
end
end
it "should merge errors via array concat if matching keys" do it "should merge errors via array concat if matching keys" do
errors = { "foo" => ["errors!"] } errors = { "foo" => ["errors!"] }
env = { "errors" => errors } env = { "errors" => errors }