# Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: chuck.bell@oracle.com-20110128185540-bjsmexqqthrle1kw # target_branch: file:///Users/cbell/source/bzr_public/mysql-\ # utilities/ # testament_sha1: 4537275e4886fbb14d7d0831395995750e91a0cb # timestamp: 2011-01-28 13:56:06 -0500 # base_revision_id: mats.kindahl@oracle.com-20110128120528-\ # vqqmn9v82k1p5dd9 # # Begin patch === modified file 'mysql/utilities/command/dbcopy.py' --- mysql/utilities/command/dbcopy.py 2010-12-07 21:05:13 +0000 +++ mysql/utilities/command/dbcopy.py 2011-01-28 18:55:40 +0000 @@ -43,7 +43,8 @@ (skip_tables, skip_views, skip_triggers, skip_procs, skip_funcs, skip_events, skip_grants, skip_create, skip_data, copy_dir, verbose, force, quiet, - connections, debug, exclude_names, exclude_patterns) + connections, debug, exclude_names, exclude_patterns, + cur_host, cur_ip) Notes: copy_dir - a directory to use for temporary files (default is None) @@ -64,6 +65,8 @@ skip_funcs = options.get("skip_funcs", False) skip_events = options.get("skip_events", False) skip_grants = options.get("skip_grants", False) + cur_host = options.get("cur_host", None) + cur_ip = options.get("cur_ip", None) try: servers = connect_servers(src_val, dest_val, quiet, "5.1.30") @@ -92,10 +95,11 @@ source_db.check_read_access(src_val["user"], src_val["host"], skip_views, skip_procs, skip_funcs, - skip_grants, skip_events) + skip_grants, skip_events, + cur_host, cur_ip) dest_db.check_write_access(dest_val["user"], dest_val["host"], skip_views, skip_procs, skip_funcs, - skip_grants) + skip_grants, cur_host, cur_ip) except MySQLUtilError, e: raise e === modified file 'mysql/utilities/command/dbexport.py' --- mysql/utilities/command/dbexport.py 2010-12-07 21:05:13 +0000 +++ mysql/utilities/command/dbexport.py 2011-01-28 18:55:40 +0000 @@ -39,7 +39,7 @@ options[in] a dictionary containing the options for the copy: (skip_tables, skip_views, skip_triggers, skip_procs, skip_funcs, skip_events, skip_grants, skip_create, - skip_data, no_header, display, format, + skip_data, no_header, display, format, cur_host, cur_ip, debug, exclude_names, exclude_patterns) Returns bool True = success, False = error @@ -49,7 +49,8 @@ from mysql.utilities.common.server import connect_servers from mysql.utilities.common.format import format_tabular_list from mysql.utilities.common.format import format_vertical_list - + from mysql.utilities.common.tools import get_host_ip + format = options.get("format", "SQL") no_headers = options.get("no_headers", False) column_type = options.get("display", "BRIEF") @@ -62,6 +63,8 @@ skip_funcs = options.get("skip_funcs", False) skip_events = options.get("skip_events", False) skip_grants = options.get("skip_grants", False) + cur_host = options.get("cur_host", None) + cur_ip = options.get("cur_ip", None) try: servers = connect_servers(src_val, None, quiet, "5.1.30") @@ -70,14 +73,15 @@ raise e source = servers[0] - + # Check user permissions on source for all databases for db_name in db_list: try: source_db = Database(source, db_name) source_db.check_read_access(src_val["user"], src_val["host"], skip_views, skip_procs, skip_funcs, - skip_grants, skip_events) + skip_grants, skip_events, + cur_host, cur_ip) except MySQLUtilError, e: raise e @@ -281,7 +285,7 @@ (skip_tables, skip_views, skip_triggers, skip_procs, skip_funcs, skip_events, skip_grants, skip_create, skip_data, no_header, display, format, file_per_tbl, - and debug) + cur_host, cur_ip, and debug) Returns bool True = success, False = error """ @@ -302,6 +306,8 @@ skip_funcs = options.get("skip_funcs", False) skip_events = options.get("skip_events", False) skip_grants = options.get("skip_grants", False) + cur_host = options.get("cur_host", None) + cur_ip = options.get("cur_ip", None) try: servers = connect_servers(src_val, None, quiet, "5.1.30") @@ -316,7 +322,8 @@ source_db = Database(source, db_name) source_db.check_read_access(src_val["user"], src_val["host"], skip_views, skip_procs, skip_funcs, - skip_grants, skip_events) + skip_grants, skip_events, + cur_host, cur_ip) except MySQLUtilError, e: raise e === modified file 'mysql/utilities/command/dbimport.py' --- mysql/utilities/command/dbimport.py 2010-12-03 21:52:48 +0000 +++ mysql/utilities/command/dbimport.py 2011-01-28 18:55:40 +0000 @@ -711,7 +711,8 @@ options[in] a dictionary containing the options for the import: (skip_tables, skip_views, skip_triggers, skip_procs, skip_funcs, skip_events, skip_grants, skip_create, - skip_data, no_header, display, format, and debug) + skip_data, no_header, display, format, + cur_host, cur_ip, and debug) Returns bool True = success, False = error """ @@ -763,6 +764,8 @@ dryrun = options.get("dryrun", False) do_drop = options.get("do_drop", False) skip_blobs = options.get("skip_blobs", False) + cur_host = options.get("cur_host", None) + cur_ip = options.get("cur_ip", None) # Attempt to connect to the destination server try: @@ -821,7 +824,8 @@ options.get("skip_views", False), options.get("skip_procs", False), options.get("skip_funcs", False), - options.get("skip_grants", False)) + options.get("skip_grants", False), + cur_host, cur_ip) except MySQLUtilError, e: raise e === modified file 'mysql/utilities/common/database.py' --- mysql/utilities/common/database.py 2010-12-07 19:17:53 +0000 +++ mysql/utilities/common/database.py 2011-01-28 18:55:40 +0000 @@ -803,26 +803,29 @@ return res - def _check_user_permissions(self, uname, host, access): + def _check_user_permissions(self, uname, host, access, + cur_host=None, cur_ip=None): """ Check user permissions for a given privilege uname[in] user name to check host[in] host name of connection acess[in] privilege to check (e.g. "SELECT") + cur_host[in] The connecting hostname + cur_ip[in] The connecting ip address Returns True if user has permission, False if not """ from mysql.utilities.common.user import User - user = User(self.source, uname+'@'+host) + user = User(self.source, uname+'@'+host, cur_host, cur_ip) result = user.has_privilege(access[0], '*', access[1]) return result def check_read_access(self, user, host, skip_views, skip_proc, skip_func, skip_grants, - skip_events): + skip_events, cur_host=None, cur_ip=None): """ Check access levels for reading database objects This method will check the user's permission levels for copying a @@ -834,10 +837,12 @@ user[in] user name to check host[in] host name to check skip_views[in] True = no views processed - skup_proc[in] True = no procedures processed + skip_proc[in] True = no procedures processed skip_func[in] True = no functions processed skip_grants[in] True = no grants processed skip_events[in] True = no events processed + cur_host[in] The connecting hostname + cur_ip[in] The connecting ip address Returns True if user has permissions and raises a MySQLUtilError if the user does not have permission with a message that includes @@ -863,7 +868,8 @@ # Check permissions on source for priv in source_privs: - if not self._check_user_permissions(user, host, priv): + if not self._check_user_permissions(user, host, priv, + cur_host, cur_ip): raise MySQLUtilError("User %s on the %s server does not have " "permissions to read all objects in %s. " % (user, self.source.role, self.db_name) + @@ -874,7 +880,8 @@ def check_write_access(self, user, host, skip_views, - skip_proc, skip_func, skip_grants): + skip_proc, skip_func, skip_grants, + cur_host=None, cur_ip=None): """ Check access levels for creating and writing database objects This method will check the user's permission levels for copying a @@ -886,9 +893,11 @@ user[in] user name to check host[in] host name to check skip_views[in] True = no views processed - skup_proc[in] True = no procedures processed + skip_proc[in] True = no procedures processed skip_func[in] True = no functions processed skip_grants[in] True = no grants processed + cur_host[in] The connecting hostname + cur_ip[in] The connecting ip address Returns True if user has permissions and raises a MySQLUtilError if the user does not have permission with a message that includes @@ -904,7 +913,8 @@ # Check privileges on destination for priv in dest_privs: - if not self._check_user_permissions(user, host, priv): + if not self._check_user_permissions(user, host, priv, + cur_host, cur_ip): raise MySQLUtilError("User %s on the %s server does not " "have permissions to create all objects " "in %s. User needs %s privilege on %s." % === modified file 'mysql/utilities/common/tools.py' --- mysql/utilities/common/tools.py 2010-10-15 20:54:15 +0000 +++ mysql/utilities/common/tools.py 2011-01-28 18:55:40 +0000 @@ -69,3 +69,29 @@ return None + +def get_host_ip(host): + """Get the host name and IP address of the machine. + + This method returns the host and IP address of the client. It will + return the host as the IP if the IP address cannot be determined. + + host[in] The default host name to use + + Returns tuple (host, ip) + """ + import socket + + cur_host = socket.gethostname() + cur_ip = socket.gethostbyname(cur_host) + nodes = cur_ip.split('.') + if nodes[0] == '127': + try: + s = socket.socket() + s.connect(('mysql.com', 80)) + cur_ip = s.getsockname()[0] + s.close() + except: + # something failed here. skip this step + cur_ip = host + return (cur_host, cur_ip) === modified file 'mysql/utilities/common/user.py' --- mysql/utilities/common/user.py 2010-11-23 20:05:53 +0000 +++ mysql/utilities/common/user.py 2011-01-28 18:55:40 +0000 @@ -54,18 +54,23 @@ """ - def __init__(self, server1, user, verbose=False): + def __init__(self, server1, user, verbose=False, + cur_host=None, cur_ip=None): """Constructor server1[in] Server class user[in] MySQL user credentials string (user@host:passwd) verbose[in] print extra data during operations (optional) default value = False + cur_host[in] The connecting host name (default is None) + cur_ip[in] The connecting host IP address (default is None) """ self.server1 = server1 self.user, self.passwd, self.host = parse_user_host(user) self.verbose = verbose + self.cur_ip = cur_ip + self.cur_host = cur_host def create(self, new_user=None): """Create the user @@ -152,22 +157,30 @@ returns result set or None if no grants defined """ + def _run_grant_query(grants, user, host): + if host is not None: + try: + res = self.server1.exec_query("SHOW GRANTS FOR '%s'@'%s'" % + (user, host)) + for grant in res: + grants.append(grant) + except MySQLUtilError, e: + pass # Error here is ok - no grants found. + grants = [] - try: - res = self.server1.exec_query("SHOW GRANTS FOR '%s'@'%s'" % - (self.user, self.host)) - for grant in res: - grants.append(grant) - except MySQLUtilError, e: - pass # Error here is ok - no grants found. + _run_grant_query(grants, self.user, self.host) + + # If connecting remotely, get remote grants + if self.cur_host is not None or self.cur_ip is not None: + if self.cur_host != self.host and self.cur_ip != self.host and \ + self.host != 'localhost' and self.host.split(".")[0] != '127': + _run_grant_query(grants, self.user, self.cur_host) + _run_grant_query(grants, self.user, self.cur_ip) + + # get global grants if globals: - try: - res = self.server1.exec_query("SHOW GRANTS FOR '%s'" % \ - self.user + "@'%'") - for grant in res: - grants.append(grant) - except MySQLUtilError, e: - pass # Error here is ok - no grants found. + _run_grant_query(grants, self.user, "%"); + return grants def has_privilege(self, db, obj, access): === modified file 'scripts/mysqldbcopy.py' --- scripts/mysqldbcopy.py 2010-12-07 19:34:27 +0000 +++ scripts/mysqldbcopy.py 2011-01-28 18:55:40 +0000 @@ -32,6 +32,7 @@ from mysql.utilities.common.options import parse_connection, add_skip_options from mysql.utilities.common.options import add_verbosity, check_verbosity from mysql.utilities.common.options import check_skip_options +from mysql.utilities.common.tools import get_host_ip from mysql.utilities.exception import MySQLUtilError # Constants @@ -137,6 +138,21 @@ print "WARNING: Cannot parse exclude list. " + \ "Proceeding without exclusions." +# Parse source connection values +try: + source_values = parse_connection(opt.source) +except: + parser.error("Source connection values invalid or cannot be parsed.") + +# Parse destination connection values +try: + dest_values = parse_connection(opt.destination) +except: + parser.error("Destination connection values invalid or cannot be parsed.") + +# Get connecting host and ip +cur_host, cur_ip = get_host_ip(source_values["host"]) + # Set options for database operations. options = { "skip_tables" : "TABLES" in skips, @@ -155,21 +171,11 @@ "threads" : opt.threads, "debug" : opt.verbosity == 3, "exclude_names" : exclude_object_names, - "exclude_patterns" : exclude_objects + "exclude_patterns" : exclude_objects, + "cur_host" : cur_host, + "cur_ip" : cur_ip } -# Parse source connection values -try: - source_values = parse_connection(opt.source) -except: - parser.error("Source connection values invalid or cannot be parsed.") - -# Parse destination connection values -try: - dest_values = parse_connection(opt.destination) -except: - parser.error("Destination connection values invalid or cannot be parsed.") - # Build list of databases to copy db_list = [] for db in args: === modified file 'scripts/mysqldbexport.py' --- scripts/mysqldbexport.py 2010-12-07 21:05:13 +0000 +++ scripts/mysqldbexport.py 2011-01-28 18:55:40 +0000 @@ -32,6 +32,7 @@ from mysql.utilities.common.options import setup_common_options from mysql.utilities.common.options import add_skip_options, check_skip_options from mysql.utilities.common.options import add_verbosity, check_verbosity +from mysql.utilities.common.tools import get_host_ip from mysql.utilities.exception import MySQLUtilError # Constants @@ -199,6 +200,15 @@ print "ERROR: You cannot use --export=data and --skip-data when exporting " \ "table data." exit(1) + +# Parse server connection values +try: + server_values = parse_connection(opt.server) +except: + parser.error("Server connection values invalid or cannot be parsed.") + +# Get connecting host and ip +cur_host, cur_ip = get_host_ip(server_values["host"]) # Set options for database operations. options = { @@ -221,15 +231,11 @@ "debug" : opt.verbosity >= 3, "file_per_tbl" : opt.file_per_tbl, "exclude_names" : exclude_object_names, - "exclude_patterns" : exclude_objects + "exclude_patterns" : exclude_objects, + "cur_host" : cur_host, + "cur_ip" : cur_ip } -# Parse server connection values -try: - server_values = parse_connection(opt.server) -except: - parser.error("Server connection values invalid or cannot be parsed.") - # Build list of databases to copy db_list = [] for db in args: === modified file 'scripts/mysqldbimport.py' --- scripts/mysqldbimport.py 2010-12-03 21:52:48 +0000 +++ scripts/mysqldbimport.py 2011-01-28 18:55:40 +0000 @@ -32,6 +32,7 @@ from mysql.utilities.common.options import setup_common_options from mysql.utilities.common.options import add_skip_options, check_skip_options from mysql.utilities.common.options import add_verbosity, check_verbosity +from mysql.utilities.common.tools import get_host_ip from mysql.utilities.exception import MySQLUtilError # Constants @@ -167,6 +168,15 @@ print "ERROR: You cannot combine --drop-first and --skip=create_db." exit (1) +# Parse server connection values +try: + server_values = parse_connection(opt.server) +except: + parser.error("Server connection values invalid or cannot be parsed.") + +# Get connecting host and ip +cur_host, cur_ip = get_host_ip(server_values["host"]) + # Set options for database operations. options = { "skip_tables" : "TABLES" in skips, @@ -187,15 +197,11 @@ "do_drop" : opt.do_drop, "quiet" : opt.quiet, "verbosity" : opt.verbosity, - "debug" : opt.verbosity >= 3 + "debug" : opt.verbosity >= 3, + "cur_host" : cur_host, + "cur_ip" : cur_ip } -# Parse server connection values -try: - server_values = parse_connection(opt.server) -except: - parser.error("Server connection values invalid or cannot be parsed.") - # Build list of files to import file_list = [] for file_name in args: # Begin bundle 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