lib/chef/knife/xargs.rb in knife-18.1.0 vs lib/chef/knife/xargs.rb in knife-18.2.5
- old
+ new
@@ -1,282 +1,282 @@
-#
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require_relative "../chef_fs/knife"
-
-class Chef
- class Knife
- class Xargs < Chef::ChefFS::Knife
- banner "knife xargs [COMMAND] (options)"
-
- category "path-based"
-
- deps do
- require "chef/chef_fs/file_system" unless defined?(Chef::ChefFS::FileSystem)
- require "chef/chef_fs/file_system/exceptions" unless defined?(Chef::ChefFS::FileSystem::Exceptions)
- end
-
- # TODO modify to remote-only / local-only pattern (more like delete)
- option :local,
- long: "--local",
- boolean: true,
- description: "Xargs local files instead of remote."
-
- option :patterns,
- long: "--pattern [PATTERN]",
- short: "-p [PATTERN]",
- description: "Pattern on command line (if these are not specified, a list of patterns is expected on standard input). Multiple patterns may be passed in this way.",
- arg_arity: [1, -1]
-
- option :diff,
- long: "--[no-]diff",
- default: true,
- boolean: true,
- description: "Whether to show a diff when files change (default: true)."
-
- option :dry_run,
- long: "--dry-run",
- boolean: true,
- description: "Prevents changes from actually being uploaded to the server."
-
- option :force,
- long: "--[no-]force",
- boolean: true,
- default: false,
- description: "Force upload of files even if they are not changed (quicker and harmless, but doesn't print out what it changed)."
-
- option :replace_first,
- long: "--replace-first REPLACESTR",
- short: "-J REPLACESTR",
- description: "String to replace with filenames. -J will only replace the FIRST occurrence of the replacement string."
-
- option :replace_all,
- long: "--replace REPLACESTR",
- short: "-I REPLACESTR",
- description: "String to replace with filenames. -I will replace ALL occurrence of the replacement string."
-
- option :max_arguments_per_command,
- long: "--max-args MAXARGS",
- short: "-n MAXARGS",
- description: "Maximum number of arguments per command line."
-
- option :max_command_line,
- long: "--max-chars LENGTH",
- short: "-s LENGTH",
- description: "Maximum size of command line, in characters."
-
- option :verbose_commands,
- short: "-t",
- description: "Print command to be run on the command line."
-
- option :null_separator,
- short: "-0",
- boolean: true,
- description: "Use the NULL character (\0) as a separator, instead of whitespace."
-
- def run
- error = false
- # Get the matches (recursively)
- files = []
- pattern_args_from(get_patterns).each do |pattern|
- Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern).each do |result|
- if result.dir?
- # TODO option to include directories
- ui.warn "#{format_path(result)}: is a directory. Will not run #{command} on it."
- else
- files << result
- ran = false
-
- # If the command would be bigger than max command line, back it off a bit
- # and run a slightly smaller command (with one less arg)
- if config[:max_command_line]
- command, tempfiles = create_command(files)
- begin
- if command.length > config[:max_command_line].to_i
- if files.length > 1
- command, tempfiles_minus_one = create_command(files[0..-2])
- begin
- error = true if xargs_files(command, tempfiles_minus_one)
- files = [ files[-1] ]
- ran = true
- ensure
- destroy_tempfiles(tempfiles)
- end
- else
- error = true if xargs_files(command, tempfiles)
- files = [ ]
- ran = true
- end
- end
- ensure
- destroy_tempfiles(tempfiles)
- end
- end
-
- # If the command has hit the limit for the # of arguments, run it
- if !ran && config[:max_arguments_per_command] && files.size >= config[:max_arguments_per_command].to_i
- command, tempfiles = create_command(files)
- begin
- error = true if xargs_files(command, tempfiles)
- files = []
- ran = true
- ensure
- destroy_tempfiles(tempfiles)
- end
- end
- end
- end
- end
-
- # Any leftovers commands shall be run
- if files.size > 0
- command, tempfiles = create_command(files)
- begin
- error = true if xargs_files(command, tempfiles)
- ensure
- destroy_tempfiles(tempfiles)
- end
- end
-
- if error
- exit 1
- end
- end
-
- def get_patterns
- if config[:patterns]
- [ config[:patterns] ].flatten
- elsif config[:null_separator]
- stdin.binmode
- stdin.read.split("\000")
- else
- stdin.read.split(/\s+/)
- end
- end
-
- def create_command(files)
- command = name_args.join(" ")
-
- # Create the (empty) tempfiles
- tempfiles = {}
- begin
- # Create the temporary files
- files.each do |file|
- tempfile = Tempfile.new(file.name)
- tempfiles[tempfile] = { file: file }
- end
- rescue
- destroy_tempfiles(files)
- raise
- end
-
- # Create the command
- paths = tempfiles.keys.map(&:path).join(" ")
- if config[:replace_all]
- final_command = command.gsub(config[:replace_all], paths)
- elsif config[:replace_first]
- final_command = command.sub(config[:replace_first], paths)
- else
- final_command = "#{command} #{paths}"
- end
-
- [final_command, tempfiles]
- end
-
- def destroy_tempfiles(tempfiles)
- # Unlink the files now that we're done with them
- tempfiles.each_key(&:close!)
- end
-
- def xargs_files(command, tempfiles)
- error = false
- # Create the temporary files
- tempfiles.each_pair do |tempfile, file|
-
- value = file[:file].read
- file[:value] = value
- tempfile.open
- tempfile.write(value)
- tempfile.close
- rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e
- ui.error "#{format_path(e.entry)}: #{e.reason}."
- error = true
- tempfile.close!
- tempfiles.delete(tempfile)
- next
- rescue Chef::ChefFS::FileSystem::NotFoundError => e
- ui.error "#{format_path(e.entry)}: No such file or directory"
- error = true
- tempfile.close!
- tempfiles.delete(tempfile)
- next
-
- end
-
- return error if error && tempfiles.size == 0
-
- # Run the command
- if config[:verbose_commands] || Chef::Config[:verbosity] && Chef::Config[:verbosity] >= 1
- output sub_filenames(command, tempfiles)
- end
- command_output = `#{command}`
- command_output = sub_filenames(command_output, tempfiles)
- stdout.write command_output
-
- # Check if the output is different
- tempfiles.each_pair do |tempfile, file|
- # Read the new output
- new_value = IO.binread(tempfile.path)
-
- # Upload the output if different
- if config[:force] || new_value != file[:value]
- if config[:dry_run]
- output "Would update #{format_path(file[:file])}"
- else
- file[:file].write(new_value)
- output "Updated #{format_path(file[:file])}"
- end
- end
-
- # Print a diff of what was uploaded
- if config[:diff] && new_value != file[:value]
- old_file = Tempfile.open(file[:file].name)
- begin
- old_file.write(file[:value])
- old_file.close
-
- diff = `diff -u #{old_file.path} #{tempfile.path}`
- diff.gsub!(old_file.path, "#{format_path(file[:file])} (old)")
- diff.gsub!(tempfile.path, "#{format_path(file[:file])} (new)")
- stdout.write diff
- ensure
- old_file.close!
- end
- end
- end
-
- error
- end
-
- def sub_filenames(str, tempfiles)
- tempfiles.each_pair do |tempfile, file|
- str = str.gsub(tempfile.path, format_path(file[:file]))
- end
- str
- end
-
- end
- end
-end
+#
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../chef_fs/knife"
+
+class Chef
+ class Knife
+ class Xargs < Chef::ChefFS::Knife
+ banner "knife xargs [COMMAND] (options)"
+
+ category "path-based"
+
+ deps do
+ require "chef/chef_fs/file_system" unless defined?(Chef::ChefFS::FileSystem)
+ require "chef/chef_fs/file_system/exceptions" unless defined?(Chef::ChefFS::FileSystem::Exceptions)
+ end
+
+ # TODO modify to remote-only / local-only pattern (more like delete)
+ option :local,
+ long: "--local",
+ boolean: true,
+ description: "Xargs local files instead of remote."
+
+ option :patterns,
+ long: "--pattern [PATTERN]",
+ short: "-p [PATTERN]",
+ description: "Pattern on command line (if these are not specified, a list of patterns is expected on standard input). Multiple patterns may be passed in this way.",
+ arg_arity: [1, -1]
+
+ option :diff,
+ long: "--[no-]diff",
+ default: true,
+ boolean: true,
+ description: "Whether to show a diff when files change (default: true)."
+
+ option :dry_run,
+ long: "--dry-run",
+ boolean: true,
+ description: "Prevents changes from actually being uploaded to the server."
+
+ option :force,
+ long: "--[no-]force",
+ boolean: true,
+ default: false,
+ description: "Force upload of files even if they are not changed (quicker and harmless, but doesn't print out what it changed)."
+
+ option :replace_first,
+ long: "--replace-first REPLACESTR",
+ short: "-J REPLACESTR",
+ description: "String to replace with filenames. -J will only replace the FIRST occurrence of the replacement string."
+
+ option :replace_all,
+ long: "--replace REPLACESTR",
+ short: "-I REPLACESTR",
+ description: "String to replace with filenames. -I will replace ALL occurrence of the replacement string."
+
+ option :max_arguments_per_command,
+ long: "--max-args MAXARGS",
+ short: "-n MAXARGS",
+ description: "Maximum number of arguments per command line."
+
+ option :max_command_line,
+ long: "--max-chars LENGTH",
+ short: "-s LENGTH",
+ description: "Maximum size of command line, in characters."
+
+ option :verbose_commands,
+ short: "-t",
+ description: "Print command to be run on the command line."
+
+ option :null_separator,
+ short: "-0",
+ boolean: true,
+ description: "Use the NULL character (\0) as a separator, instead of whitespace."
+
+ def run
+ error = false
+ # Get the matches (recursively)
+ files = []
+ pattern_args_from(get_patterns).each do |pattern|
+ Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern).each do |result|
+ if result.dir?
+ # TODO option to include directories
+ ui.warn "#{format_path(result)}: is a directory. Will not run #{command} on it."
+ else
+ files << result
+ ran = false
+
+ # If the command would be bigger than max command line, back it off a bit
+ # and run a slightly smaller command (with one less arg)
+ if config[:max_command_line]
+ command, tempfiles = create_command(files)
+ begin
+ if command.length > config[:max_command_line].to_i
+ if files.length > 1
+ command, tempfiles_minus_one = create_command(files[0..-2])
+ begin
+ error = true if xargs_files(command, tempfiles_minus_one)
+ files = [ files[-1] ]
+ ran = true
+ ensure
+ destroy_tempfiles(tempfiles)
+ end
+ else
+ error = true if xargs_files(command, tempfiles)
+ files = [ ]
+ ran = true
+ end
+ end
+ ensure
+ destroy_tempfiles(tempfiles)
+ end
+ end
+
+ # If the command has hit the limit for the # of arguments, run it
+ if !ran && config[:max_arguments_per_command] && files.size >= config[:max_arguments_per_command].to_i
+ command, tempfiles = create_command(files)
+ begin
+ error = true if xargs_files(command, tempfiles)
+ files = []
+ ran = true
+ ensure
+ destroy_tempfiles(tempfiles)
+ end
+ end
+ end
+ end
+ end
+
+ # Any leftovers commands shall be run
+ if files.size > 0
+ command, tempfiles = create_command(files)
+ begin
+ error = true if xargs_files(command, tempfiles)
+ ensure
+ destroy_tempfiles(tempfiles)
+ end
+ end
+
+ if error
+ exit 1
+ end
+ end
+
+ def get_patterns
+ if config[:patterns]
+ [ config[:patterns] ].flatten
+ elsif config[:null_separator]
+ stdin.binmode
+ stdin.read.split("\000")
+ else
+ stdin.read.split(/\s+/)
+ end
+ end
+
+ def create_command(files)
+ command = name_args.join(" ")
+
+ # Create the (empty) tempfiles
+ tempfiles = {}
+ begin
+ # Create the temporary files
+ files.each do |file|
+ tempfile = Tempfile.new(file.name)
+ tempfiles[tempfile] = { file: file }
+ end
+ rescue
+ destroy_tempfiles(files)
+ raise
+ end
+
+ # Create the command
+ paths = tempfiles.keys.map(&:path).join(" ")
+ if config[:replace_all]
+ final_command = command.gsub(config[:replace_all], paths)
+ elsif config[:replace_first]
+ final_command = command.sub(config[:replace_first], paths)
+ else
+ final_command = "#{command} #{paths}"
+ end
+
+ [final_command, tempfiles]
+ end
+
+ def destroy_tempfiles(tempfiles)
+ # Unlink the files now that we're done with them
+ tempfiles.each_key(&:close!)
+ end
+
+ def xargs_files(command, tempfiles)
+ error = false
+ # Create the temporary files
+ tempfiles.each_pair do |tempfile, file|
+
+ value = file[:file].read
+ file[:value] = value
+ tempfile.open
+ tempfile.write(value)
+ tempfile.close
+ rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e
+ ui.error "#{format_path(e.entry)}: #{e.reason}."
+ error = true
+ tempfile.close!
+ tempfiles.delete(tempfile)
+ next
+ rescue Chef::ChefFS::FileSystem::NotFoundError => e
+ ui.error "#{format_path(e.entry)}: No such file or directory"
+ error = true
+ tempfile.close!
+ tempfiles.delete(tempfile)
+ next
+
+ end
+
+ return error if error && tempfiles.size == 0
+
+ # Run the command
+ if config[:verbose_commands] || Chef::Config[:verbosity] && Chef::Config[:verbosity] >= 1
+ output sub_filenames(command, tempfiles)
+ end
+ command_output = `#{command}`
+ command_output = sub_filenames(command_output, tempfiles)
+ stdout.write command_output
+
+ # Check if the output is different
+ tempfiles.each_pair do |tempfile, file|
+ # Read the new output
+ new_value = IO.binread(tempfile.path)
+
+ # Upload the output if different
+ if config[:force] || new_value != file[:value]
+ if config[:dry_run]
+ output "Would update #{format_path(file[:file])}"
+ else
+ file[:file].write(new_value)
+ output "Updated #{format_path(file[:file])}"
+ end
+ end
+
+ # Print a diff of what was uploaded
+ if config[:diff] && new_value != file[:value]
+ old_file = Tempfile.open(file[:file].name)
+ begin
+ old_file.write(file[:value])
+ old_file.close
+
+ diff = `diff -u #{old_file.path} #{tempfile.path}`
+ diff.gsub!(old_file.path, "#{format_path(file[:file])} (old)")
+ diff.gsub!(tempfile.path, "#{format_path(file[:file])} (new)")
+ stdout.write diff
+ ensure
+ old_file.close!
+ end
+ end
+ end
+
+ error
+ end
+
+ def sub_filenames(str, tempfiles)
+ tempfiles.each_pair do |tempfile, file|
+ str = str.gsub(tempfile.path, format_path(file[:file]))
+ end
+ str
+ end
+
+ end
+ end
+end