# GOV.UK Mini Environment Admin # Copyright © 2018 Christopher Baines # # This file is part of the GOV.UK Mini Environment Admin. # # The GOV.UK Mini Environment Admin is free software: you can # redistribute it and/or modify it under the terms of the GNU Affero # General Public License as published by the Free Software Foundation, # either version 3 of the License, or (at your option) any later # version. # # The GOV.UK Mini Environment Admin is distributed in the hope that it # will be useful, but WITHOUT ANY WARRANTY; without even the implied # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public # License along with the GOV.UK Mini Environment Admin. If not, see # . require 'open3' require 'shellwords' require 'tempfile' module ShellUtils def run_command( *command, run_remotely_on_host: nil, environment_variables: {} ) if run_remotely_on_host command = command.map do |arg| Shellwords.escape(arg) end identity_file = Tempfile.new( 'private-identity-file', Rails.root.join('tmp') ) identity_file.write(run_remotely_on_host.private_key) identity_file.close command = [ 'ssh', # Use a automatically trust on first use model '-o', 'StrictHostKeyChecking=no', '-o', 'ServerAliveInterval=20', '-i', identity_file.path, run_remotely_on_host.user_at_address, *environment_variables_to_shell_arguments(environment_variables), *command ] local_environment_variables = {} else local_environment_variables = environment_variables end logger.info("#{self.class}: Running command #{command.join(' ')}") Open3.popen2e( local_environment_variables, *command ) do |_stdin, stdout_and_stderr, wait_thr| logger.info("#{self.class}: commmand running, pid #{wait_thr.pid}") output = [] stdout_and_stderr.each_line do |line| logger.info(self.class) { line.chomp } output << line end exit_status = wait_thr.value unless exit_status == 0 logger.error(self.class) { "failed, exit status #{exit_status}" } identity_file.unlink if identity_file raise "Running #{command.join(' ')} failed:\n\n#{output.join}\n" end identity_file.unlink if identity_file output end end def read_json_file(filename, from_remote_host: nil) if from_remote_host identity_file = Tempfile.new( 'private-identity-file', Rails.root.join('tmp') ) identity_file.write(from_remote_host.private_key) identity_file.close command = [ 'ssh', from_remote_host.user_at_address, # Use a automatically trust on first use model '-o', 'StrictHostKeyChecking=no', '-i', identity_file.path, 'cat', filename ] stdout_str, status = Open3.capture2(*command) identity_file.unlink if identity_file unless status.exitstatus == 0 logger.error(self.class) { "failed, exit status #{exit_status}" } raise "Running #{command.join(' ')} failed:\n\n#{output.join}\n" end JSON.parse(stdout_str) else JSON.parse(File.read(filename)) end end def hash_to_arguments(hash) hash.flat_map do |(key, value)| transformed_key = key.to_s.tr('_', '-') if value == true ["--#{transformed_key}"] elsif value.kind_of?(Array) value.map { |x| "--#{transformed_key}=#{x}" } else ["--#{transformed_key}=#{value}"] end end end def environment_variables_to_shell_arguments(environment_variables) environment_variables.map do |key, value| "#{key}=\"#{value}\"" end end end