From 77031a79b7409e7b4758d94eb2666c1737c583cb Mon Sep 17 00:00:00 2001 From: Hasyimi Bahrudin Date: Thu, 10 Dec 2015 15:58:02 +0800 Subject: [PATCH] pushes/local-exec: add args config --- plugins/pushes/local-exec/config.rb | 29 +++++++ plugins/pushes/local-exec/locales/en.yml | 1 + plugins/pushes/local-exec/push.rb | 25 ++++-- .../plugins/pushes/local-exec/config_test.rb | 79 +++++++++++++++++++ .../plugins/pushes/local-exec/push_test.rb | 29 +++++-- .../docs/source/v2/push/local-exec.html.md | 9 +++ 6 files changed, 159 insertions(+), 13 deletions(-) diff --git a/plugins/pushes/local-exec/config.rb b/plugins/pushes/local-exec/config.rb index 747ff8925..d62dfa8fa 100644 --- a/plugins/pushes/local-exec/config.rb +++ b/plugins/pushes/local-exec/config.rb @@ -10,14 +10,24 @@ module VagrantPlugins # @return [String] attr_accessor :inline + # The arguments to provide when executing the script. + # @return [Array] + attr_accessor :args + def initialize @script = UNSET_VALUE @inline = UNSET_VALUE + @args = UNSET_VALUE end def finalize! @script = nil if @script == UNSET_VALUE @inline = nil if @inline == UNSET_VALUE + @args = nil if @args == UNSET_VALUE + + if @args && args_valid? + @args = @args.is_a?(Array) ? @args.map { |a| a.to_s } : @args.to_s + end end def validate(machine) @@ -33,6 +43,10 @@ module VagrantPlugins errors << I18n.t("local_exec_push.errors.cannot_specify_script_and_inline") end + if !args_valid? + errors << I18n.t("local_exec_push.errors.args_bad_type") + end + { "Local Exec push" => errors } end @@ -43,6 +57,21 @@ module VagrantPlugins def missing?(obj) obj.to_s.strip.empty? end + + # Args are optional, but if they're provided we only support them as a + # string or as an array. + def args_valid? + return true if !args + return true if args.is_a?(String) + return true if args.is_a?(Fixnum) + if args.is_a?(Array) + args.each do |a| + return false if !a.kind_of?(String) && !a.kind_of?(Fixnum) + end + + return true + end + end end end end diff --git a/plugins/pushes/local-exec/locales/en.yml b/plugins/pushes/local-exec/locales/en.yml index 08808a879..3ae196d9c 100644 --- a/plugins/pushes/local-exec/locales/en.yml +++ b/plugins/pushes/local-exec/locales/en.yml @@ -20,3 +20,4 @@ en: config.push.define "local-exec" do |push| push.%{attribute} = "..." end + args_bad_type: "Local-exec push `args` must be a string or array." \ No newline at end of file diff --git a/plugins/pushes/local-exec/push.rb b/plugins/pushes/local-exec/push.rb index d4bc75176..503957c9e 100644 --- a/plugins/pushes/local-exec/push.rb +++ b/plugins/pushes/local-exec/push.rb @@ -9,20 +9,20 @@ module VagrantPlugins class Push < Vagrant.plugin("2", :push) def push if config.inline - execute_inline!(config.inline) + execute_inline!(config.inline, config.args) else - execute_script!(config.script) + execute_script!(config.script, config.args) end end # Execute the inline script by writing it to a tempfile and executing. - def execute_inline!(inline) + def execute_inline!(inline, args) script = Tempfile.new(["vagrant-local-exec-script", ".sh"]) script.write(inline) script.rewind script.close - execute_script!(script.path) + execute_script!(script.path, args) ensure if script script.close @@ -31,10 +31,18 @@ module VagrantPlugins end # Execute the script, expanding the path relative to the current env root. - def execute_script!(path) + def execute_script!(path, args) path = File.expand_path(path, env.root_path) FileUtils.chmod("+x", path) - execute!(path) + + if args.is_a?(String) + args = " #{args.to_s}" + elsif args.is_a?(Array) + args = args.map { |a| quote_and_escape(a) } + args = " #{args.join(" ")}" + end + + execute!("#{path}#{args}") end # Execute the script, raising an exception if it fails. @@ -48,6 +56,11 @@ module VagrantPlugins private + # Quote and escape strings for shell execution, thanks to Capistrano. + def quote_and_escape(text, quote = '"') + "#{quote}#{text.gsub(/#{quote}/) { |m| "#{m}\\#{m}#{m}" }}#{quote}" + end + # Run the command as exec (unix). def execute_exec!(*cmd) Vagrant::Util::SafeExec.exec(cmd[0], *cmd[1..-1]) diff --git a/test/unit/plugins/pushes/local-exec/config_test.rb b/test/unit/plugins/pushes/local-exec/config_test.rb index 045872d2f..1bde10d98 100644 --- a/test/unit/plugins/pushes/local-exec/config_test.rb +++ b/test/unit/plugins/pushes/local-exec/config_test.rb @@ -26,6 +26,13 @@ describe VagrantPlugins::LocalExecPush::Config do end end + describe "#args" do + it "defaults to nil" do + subject.finalize! + expect(subject.args).to be(nil) + end + end + describe "#validate" do before do allow(machine).to receive(:env) @@ -57,6 +64,42 @@ describe VagrantPlugins::LocalExecPush::Config do it "does not return an error" do expect(errors).to be_empty end + + it "passes with string args" do + subject.args = "a string" + expect(errors).to be_empty + end + + it "passes with fixnum args" do + subject.args = 1 + expect(errors).to be_empty + end + + it "passes with array args" do + subject.args = ["an", "array"] + expect(errors).to be_empty + end + + it "returns an error if args is neither a string nor an array" do + neither_array_nor_string = Object.new + + subject.args = neither_array_nor_string + expect(errors).to include( + I18n.t("local_exec_push.errors.args_bad_type") + ) + end + + it "handles scalar array args" do + subject.args = ["string", 1, 2] + expect(errors).to be_empty + end + + it "returns an error if args is an array with non-scalar types" do + subject.args = [[1]] + expect(errors).to include( + I18n.t("local_exec_push.errors.args_bad_type") + ) + end end end @@ -69,6 +112,42 @@ describe VagrantPlugins::LocalExecPush::Config do it "does not return an error" do expect(errors).to be_empty end + + it "passes with string args" do + subject.args = "a string" + expect(errors).to be_empty + end + + it "passes with fixnum args" do + subject.args = 1 + expect(errors).to be_empty + end + + it "passes with array args" do + subject.args = ["an", "array"] + expect(errors).to be_empty + end + + it "returns an error if args is neither a string nor an array" do + neither_array_nor_string = Object.new + + subject.args = neither_array_nor_string + expect(errors).to include( + I18n.t("local_exec_push.errors.args_bad_type") + ) + end + + it "handles scalar array args" do + subject.args = ["string", 1, 2] + expect(errors).to be_empty + end + + it "returns an error if args is an array with non-scalar types" do + subject.args = [[1]] + expect(errors).to include( + I18n.t("local_exec_push.errors.args_bad_type") + ) + end end context "when inline is not present" do diff --git a/test/unit/plugins/pushes/local-exec/push_test.rb b/test/unit/plugins/pushes/local-exec/push_test.rb index e7c04060d..00db78777 100644 --- a/test/unit/plugins/pushes/local-exec/push_test.rb +++ b/test/unit/plugins/pushes/local-exec/push_test.rb @@ -15,6 +15,7 @@ describe VagrantPlugins::LocalExecPush::Push do double("config", script: nil, inline: nil, + args: "some args", ) end @@ -37,7 +38,7 @@ describe VagrantPlugins::LocalExecPush::Push do it "executes the inline script" do expect(subject).to receive(:execute_inline!) - .with(config.inline) + .with(config.inline, config.args) subject.push end end @@ -47,7 +48,7 @@ describe VagrantPlugins::LocalExecPush::Push do it "executes the script" do expect(subject).to receive(:execute_script!) - .with(config.script) + .with(config.script, config.args) subject.push end end @@ -58,12 +59,12 @@ describe VagrantPlugins::LocalExecPush::Push do it "writes the script to a tempfile" do expect(Tempfile).to receive(:new).and_call_original - subject.execute_inline!("echo") + subject.execute_inline!("echo", config.args) end it "executes the script" do expect(subject).to receive(:execute_script!) - subject.execute_inline!("echo") + subject.execute_inline!("echo", config.args) end end @@ -76,19 +77,33 @@ describe VagrantPlugins::LocalExecPush::Push do it "expands the path relative to the machine root" do expect(subject).to receive(:execute!) .with(File.expand_path("foo.sh", env.root_path)) - subject.execute_script!("./foo.sh") + subject.execute_script!("./foo.sh", nil) end it "makes the file executable" do expect(FileUtils).to receive(:chmod) .with("+x", File.expand_path("foo.sh", env.root_path)) - subject.execute_script!("./foo.sh") + subject.execute_script!("./foo.sh", config.args) end it "calls execute!" do expect(subject).to receive(:execute!) .with(File.expand_path("foo.sh", env.root_path)) - subject.execute_script!("./foo.sh") + subject.execute_script!("./foo.sh", nil) + end + + context "when args is given" do + it "passes string args to execute!" do + expect(subject).to receive(:execute!) + .with(File.expand_path("foo.sh", env.root_path) + " " + config.args) + subject.execute_script!("./foo.sh", config.args) + end + + it "passes array args as string to execute!" do + expect(subject).to receive(:execute!) + .with(File.expand_path("foo.sh", env.root_path) + " \"one\" \"two\" \"three\"") + subject.execute_script!("./foo.sh", ["one", "two", "three"]) + end end end diff --git a/website/docs/source/v2/push/local-exec.html.md b/website/docs/source/v2/push/local-exec.html.md index 11ba906c8..2d267c4c2 100644 --- a/website/docs/source/v2/push/local-exec.html.md +++ b/website/docs/source/v2/push/local-exec.html.md @@ -27,6 +27,11 @@ options: execute. Vagrant will attempt to convert this script to an executable, but an exception will be raised if that fails. - `inline` - The inline script to execute (as a string). +- `args` (string or array) - Optional arguments to pass to the shell script when executing it + as a single string. These arguments must be written as if they were typed + directly on the command line, so be sure to escape characters, quote, + etc. as needed. You may also pass the arguments in using an array. In this + case, Vagrant will handle quoting for you. Please note - only one of the `script` and `inline` options may be specified in a single push definition. @@ -70,3 +75,7 @@ And then invoke the push with Vagrant: ```shell $ vagrant push ``` + +### Script Arguments + +Refer to [Shell Provisioner](/v2/provisioning/shell.html).