# Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: chuck.bell@oracle.com-20110210145226-okg888gevzm57336 # target_branch: file:///Users/cbell/source/bzr_public/mysql-\ # utilities/ # testament_sha1: 9f4e11a42fae67e53501eda71b643686e336d7b9 # timestamp: 2011-02-10 09:52:50 -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-02-10 14:52:26 +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-02-10 14:52:26 +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-02-10 14:52:26 +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-02-10 14:52:26 +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-02-10 14:52:26 +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-02-10 14:52:26 +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-02-10 14:52:26 +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-02-10 14:52:26 +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-02-10 14:52:26 +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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 juen5j/4u5IpwoSAR3HE8A==