module Flydata class CompatibilityCheck class CompatibilityError < StandardError end def initialize(dp_hash, de_hash=nil, options={}) @dp = dp_hash @errors=[] end def check self.methods.grep(/^check_/).each do |m| begin send(m) rescue CompatibilityError => e @errors << e end end print_errors end def print_errors return if @errors.empty? puts "There may be some compatibility issues with this current server : " @errors.each do |error| puts " * #{error.message}" end raise "Please correct these errors if you wish to run FlyData Agent" end end class AgentCompatibilityCheck < CompatibilityCheck class AgentCompatibilityError < StandardError end TCP_PORT=45326 SSL_PORT=45327 def check_outgoing_ports ports = [TCP_PORT] ports << SSL_PORT unless ENV['FLYDATA_ENV_KEY'] == 'development' url = @dp["servers"].first errors = {} ports.each do |port| begin e = TCPSocket.new(url, port) e.close rescue => e errors[port] = e end end unless errors.empty? message = "Cannot connect to outisde ports. Please check to make sure you have these outgoing ports open." errors.each do |port, e| message += " Port #{port}, Error #{e.class.name}: #{e.to_s}" end raise AgentCompatibilityError, message end end end class MysqlCompatibilityCheck < CompatibilityCheck class MysqlCompatibilityError < StandardError end SELECT_QUERY_TMPLT = "SELECT %s" #def initialize(de_hash, dump_dir=nil) def initialize(dp_hash, de_hash, options={}) super @db_opts = [:host, :port, :username, :password, :database].inject({}) {|h, sym| h[sym] = de_hash[sym.to_s]; h} @dump_dir = options['dump_dir'] || nil end def print_errors return if @errors.empty? puts "There may be some compatibility issues with your MySQL credentials: " @errors.each do |error| puts " * #{error.message}" end raise "Please correct these errors if you wish to run FlyData Sync" end def check_mysql_user_compat client = Mysql2::Client.new(@db_opts) grants_sql = "SHOW GRANTS" correct_db = ["ON (\\*|#{@db_opts[:database]})","TO '#{@db_opts[:username]}"] necessary_permission_fields= ["SELECT","RELOAD","LOCK TABLES","REPLICATION SLAVE","REPLICATION CLIENT"] all_privileges_field= ["ALL PRIVILEGES"] result = client.query(grants_sql) # Do not catch MySQL connection problem because check should stop if no MySQL connection can be made. client.close missing_priv = [] result.each do |res| # SHOW GRANTS should only return one column res_value = res.values.first if correct_db.all? {|perm| res_value.match(perm)} necessary_permission_fields.each do |priv| missing_priv << priv unless res_value.match(priv) end return true if missing_priv.empty? or all_privileges_field.all? {|d| res_value.match(d)} end end raise MysqlCompatibilityError, "The user '#{@db_opts[:username]}' does not have the correct permissions to run FlyData Sync\n * These privileges are missing: #{missing_priv.join(", ")}" end def check_mysql_protocol_tcp_compat query = "MYSQL_PWD=\"#{@db_opts[:password]}\" mysql -u #{@db_opts[:username]} -h #{@db_opts[:host]} -P #{@db_opts[:port]} #{@db_opts[:database]} -e \"SHOW GRANTS;\" --protocol=tcp" Open3.popen3(query) do |stdin, stdout, stderr| stdin.close while !stderr.eof? line = stderr.gets unless /Warning: Using a password on the command line interface can be insecure./ === line raise MysqlCompatibilityError, "Cannot connect to MySQL database. Please make sure you can connect with this command:\n $ mysql -u #{@db_opts[:username]} -h #{@db_opts[:host]} -P #{@db_opts[:port]} #{@db_opts[:database]} --protocol=tcp -p" end end end end def check_mysql_row_mode_compat sys_var_to_check = {'@@binlog_format'=>'ROW', '@@binlog_checksum'=>'NONE', '@@log_bin_use_v1_row_events'=>1} errors={} client = Mysql2::Client.new(@db_opts) begin sys_var_to_check.each_key do |sys_var| sel_query = SELECT_QUERY_TMPLT % sys_var begin result = client.query(sel_query) unless result.first[sys_var] == sys_var_to_check[sys_var] errors[sys_var]=result.first[sys_var] end rescue Mysql2::Error => e if e.message =~ /Unknown system variable/ unless e.message =~ /(binlog_checksum|log_bin_use_v1_row_events)/ errors[sys_var] = false end else raise e end end end ensure client.close end unless errors.empty? error_explanation = "" errors.each_key do |err_key| error_explanation << "\n * #{err_key} is #{errors[err_key]} but should be #{sys_var_to_check[err_key]}" end raise MysqlCompatibilityError, "These system variable(s) are not the correct value: #{error_explanation}\n Please change these system variables for FlyData Sync to run correctly" end end def check_writing_permissions write_errors = [] paths_to_check = ["~/.flydata"] paths_to_check << @dump_dir unless @dump_dir.to_s.empty? paths_to_check.each do |path| full_path = File.expand_path(path) full_path = File.dirname(full_path) unless File.directory?(full_path) write_errors << full_path unless File.writable?(full_path) end unless write_errors.empty? error_dir = write_errors.join(", ") raise MysqlCompatibilityError, "We cannot access the directories: #{error_dir}" end end end end