From 1dfa41dada30c139129cb2771b0d68c95fd84e32 Mon Sep 17 00:00:00 2001 From: Tim Burke Date: Tue, 28 Apr 2020 10:45:27 -0700 Subject: [PATCH] swift-get-nodes: Allow users to specify either quoted or unquoted paths Now that we can have null bytes in Swift paths, we need a way for operators to be able to locate such containers and objects. Our usual trick of making sure the name is properly quoted for the shell won't suffice; running something like swift-get-nodes /etc/swift/container.ring.gz $'AUTH_test/\0versions\0container' has the path get cut off after "AUTH_test/" because of how argv works. So, add a new option, --quoted, to let operators indicate that they already quoted the path. Drive-bys: * If account, container, or object are explicitly blank, treat them as though they were not provided. This provides better errors when account is explicitly blank, for example. * If account, container, or object are not provided or explicitly blank, skip printing them. This resolves abiguities about things like objects whose name is actually "None". * When displaying account, container, and object, quote them (since they may contain newlines or other control characters). Change-Id: I3d10e121b403de7533cc3671604bcbdecb02c795 Related-Change: If912f71d8b0d03369680374e8233da85d8d38f85 Closes-Bug: #1875734 Closes-Bug: #1875735 Closes-Bug: #1875736 Related-Bug: #1791302 --- bin/swift-get-nodes | 2 + swift/cli/info.py | 17 +++-- test/unit/cli/test_info.py | 148 ++++++++++++++++++++----------------- 3 files changed, 94 insertions(+), 73 deletions(-) diff --git a/bin/swift-get-nodes b/bin/swift-get-nodes index 3d944bf563..5bc21c9456 100755 --- a/bin/swift-get-nodes +++ b/bin/swift-get-nodes @@ -52,6 +52,8 @@ if __name__ == '__main__': help='Specify which policy to use') parser.add_option('-d', '--swift-dir', default='/etc/swift', dest='swift_dir', help='Path to swift directory') + parser.add_option('-Q', '--quoted', action='store_true', + help='Assume swift paths are quoted') options, args = parser.parse_args() if set_swift_dir(options.swift_dir): diff --git a/swift/cli/info.py b/swift/cli/info.py index 49c8da88e7..dc29faded6 100644 --- a/swift/cli/info.py +++ b/swift/cli/info.py @@ -57,6 +57,8 @@ def parse_get_node_args(options, args): else: raise InfoSystemExit('Ring file does not exist') + if options.quoted: + args = [urllib.parse.unquote(arg) for arg in args] if len(args) == 1: args = args[0].strip('/').split('/', 2) @@ -614,15 +616,15 @@ def print_item_locations(ring, ring_name=None, account=None, container=None, ring = POLICIES.get_object_ring(policy_index, swift_dir) ring_name = (POLICIES.get_by_name(policy_name)).ring_name - if account is None and (container is not None or obj is not None): + if (container or obj) and not account: print('No account specified') raise InfoSystemExit() - if container is None and obj is not None: + if obj and not container: print('No container specified') raise InfoSystemExit() - if account is None and part is None: + if not account and not part: print('No target specified') raise InfoSystemExit() @@ -654,8 +656,11 @@ def print_item_locations(ring, ring_name=None, account=None, container=None, print('Warning: account specified ' + 'but ring not named "account"') - print('\nAccount \t%s' % account) - print('Container\t%s' % container) - print('Object \t%s\n\n' % obj) + if account: + print('\nAccount \t%s' % urllib.parse.quote(account)) + if container: + print('Container\t%s' % urllib.parse.quote(container)) + if obj: + print('Object \t%s\n\n' % urllib.parse.quote(obj)) print_ring_locations(ring, loc, account, container, obj, part, all_nodes, policy_index=policy_index) diff --git a/test/unit/cli/test_info.py b/test/unit/cli/test_info.py index ffe10dc69a..f1a77d0b13 100644 --- a/test/unit/cli/test_info.py +++ b/test/unit/cli/test_info.py @@ -497,13 +497,10 @@ Shard Ranges (3): print_item_locations(None, partition=part, policy_name='zero', swift_dir=self.testdir) exp_part_msg = 'Partition\t%s' % part - exp_acct_msg = 'Account \tNone' - exp_cont_msg = 'Container\tNone' - exp_obj_msg = 'Object \tNone' self.assertIn(exp_part_msg, out.getvalue()) - self.assertIn(exp_acct_msg, out.getvalue()) - self.assertIn(exp_cont_msg, out.getvalue()) - self.assertIn(exp_obj_msg, out.getvalue()) + self.assertNotIn('Account', out.getvalue()) + self.assertNotIn('Container', out.getvalue()) + self.assertNotIn('Object', out.getvalue()) def test_print_item_locations_dashed_ring_name_partition(self): out = StringIO() @@ -513,13 +510,10 @@ Shard Ranges (3): ring_name='foo-bar', partition=part, swift_dir=self.testdir) exp_part_msg = 'Partition\t%s' % part - exp_acct_msg = 'Account \tNone' - exp_cont_msg = 'Container\tNone' - exp_obj_msg = 'Object \tNone' self.assertIn(exp_part_msg, out.getvalue()) - self.assertIn(exp_acct_msg, out.getvalue()) - self.assertIn(exp_cont_msg, out.getvalue()) - self.assertIn(exp_obj_msg, out.getvalue()) + self.assertNotIn('Account', out.getvalue()) + self.assertNotIn('Container', out.getvalue()) + self.assertNotIn('Object', out.getvalue()) def test_print_item_locations_account_with_ring(self): out = StringIO() @@ -533,11 +527,9 @@ Shard Ranges (3): 'but ring not named "account"' self.assertIn(exp_warning, out.getvalue()) exp_acct_msg = 'Account \t%s' % account - exp_cont_msg = 'Container\tNone' - exp_obj_msg = 'Object \tNone' self.assertIn(exp_acct_msg, out.getvalue()) - self.assertIn(exp_cont_msg, out.getvalue()) - self.assertIn(exp_obj_msg, out.getvalue()) + self.assertNotIn('Container', out.getvalue()) + self.assertNotIn('Object', out.getvalue()) def test_print_item_locations_account_no_ring(self): out = StringIO() @@ -546,11 +538,9 @@ Shard Ranges (3): print_item_locations(None, account=account, swift_dir=self.testdir) exp_acct_msg = 'Account \t%s' % account - exp_cont_msg = 'Container\tNone' - exp_obj_msg = 'Object \tNone' self.assertIn(exp_acct_msg, out.getvalue()) - self.assertIn(exp_cont_msg, out.getvalue()) - self.assertIn(exp_obj_msg, out.getvalue()) + self.assertNotIn('Container', out.getvalue()) + self.assertNotIn('Object', out.getvalue()) def test_print_item_locations_account_container_ring(self): out = StringIO() @@ -562,10 +552,9 @@ Shard Ranges (3): container=container) exp_acct_msg = 'Account \t%s' % account exp_cont_msg = 'Container\t%s' % container - exp_obj_msg = 'Object \tNone' self.assertIn(exp_acct_msg, out.getvalue()) self.assertIn(exp_cont_msg, out.getvalue()) - self.assertIn(exp_obj_msg, out.getvalue()) + self.assertNotIn('Object', out.getvalue()) def test_print_item_locations_account_container_no_ring(self): out = StringIO() @@ -576,10 +565,9 @@ Shard Ranges (3): container=container, swift_dir=self.testdir) exp_acct_msg = 'Account \t%s' % account exp_cont_msg = 'Container\t%s' % container - exp_obj_msg = 'Object \tNone' self.assertIn(exp_acct_msg, out.getvalue()) self.assertIn(exp_cont_msg, out.getvalue()) - self.assertIn(exp_obj_msg, out.getvalue()) + self.assertNotIn('Object', out.getvalue()) def test_print_item_locations_account_container_object_ring(self): out = StringIO() @@ -691,59 +679,59 @@ Shard Ranges (3): def test_parse_get_node_args(self): # Capture error messages # (without any parameters) - options = Namespace(policy_name=None, partition=None) + options = Namespace(policy_name=None, partition=None, quoted=None) args = '' self.assertRaisesMessage(InfoSystemExit, 'Need to specify policy_name or ', parse_get_node_args, options, args.split()) # a - options = Namespace(policy_name=None, partition=None) + options = Namespace(policy_name=None, partition=None, quoted=None) args = 'a' self.assertRaisesMessage(InfoSystemExit, 'Need to specify policy_name or ', parse_get_node_args, options, args.split()) # a c - options = Namespace(policy_name=None, partition=None) + options = Namespace(policy_name=None, partition=None, quoted=None) args = 'a c' self.assertRaisesMessage(InfoSystemExit, 'Need to specify policy_name or ', parse_get_node_args, options, args.split()) # a c o - options = Namespace(policy_name=None, partition=None) + options = Namespace(policy_name=None, partition=None, quoted=None) args = 'a c o' self.assertRaisesMessage(InfoSystemExit, 'Need to specify policy_name or ', parse_get_node_args, options, args.split()) # a/c - options = Namespace(policy_name=None, partition=None) + options = Namespace(policy_name=None, partition=None, quoted=None) args = 'a/c' self.assertRaisesMessage(InfoSystemExit, 'Need to specify policy_name or ', parse_get_node_args, options, args.split()) # a/c/o - options = Namespace(policy_name=None, partition=None) + options = Namespace(policy_name=None, partition=None, quoted=None) args = 'a/c/o' self.assertRaisesMessage(InfoSystemExit, 'Need to specify policy_name or ', parse_get_node_args, options, args.split()) # account container junk/test.ring.gz - options = Namespace(policy_name=None, partition=None) + options = Namespace(policy_name=None, partition=None, quoted=None) args = 'account container junk/test.ring.gz' self.assertRaisesMessage(InfoSystemExit, 'Need to specify policy_name or ', parse_get_node_args, options, args.split()) # account container object junk/test.ring.gz - options = Namespace(policy_name=None, partition=None) + options = Namespace(policy_name=None, partition=None, quoted=None) args = 'account container object junk/test.ring.gz' self.assertRaisesMessage(InfoSystemExit, 'Need to specify policy_name or ', parse_get_node_args, options, args.split()) # object.ring.gz(without any arguments i.e. a c o) - options = Namespace(policy_name=None, partition=None) + options = Namespace(policy_name=None, partition=None, quoted=None) args = 'object.ring.gz' self.assertRaisesMessage(InfoSystemExit, 'Ring file does not exist', @@ -751,55 +739,55 @@ Shard Ranges (3): # Valid policy # -P zero - options = Namespace(policy_name='zero', partition=None) + options = Namespace(policy_name='zero', partition=None, quoted=None) args = '' self.assertRaisesMessage(InfoSystemExit, 'No target specified', parse_get_node_args, options, args.split()) # -P one a/c/o - options = Namespace(policy_name='one', partition=None) + options = Namespace(policy_name='one', partition=None, quoted=None) args = 'a/c/o' ring_path, args = parse_get_node_args(options, args.split()) self.assertIsNone(ring_path) self.assertEqual(args, ['a', 'c', 'o']) # -P one account container photos/cat.jpg - options = Namespace(policy_name='one', partition=None) + options = Namespace(policy_name='one', partition=None, quoted=None) args = 'account container photos/cat.jpg' ring_path, args = parse_get_node_args(options, args.split()) self.assertIsNone(ring_path) self.assertEqual(args, ['account', 'container', 'photos/cat.jpg']) # -P one account/container/photos/cat.jpg - options = Namespace(policy_name='one', partition=None) + options = Namespace(policy_name='one', partition=None, quoted=None) args = 'account/container/photos/cat.jpg' ring_path, args = parse_get_node_args(options, args.split()) self.assertIsNone(ring_path) self.assertEqual(args, ['account', 'container', 'photos/cat.jpg']) # -P one account/container/junk/test.ring.gz(object endswith 'ring.gz') - options = Namespace(policy_name='one', partition=None) + options = Namespace(policy_name='one', partition=None, quoted=None) args = 'account/container/junk/test.ring.gz' ring_path, args = parse_get_node_args(options, args.split()) self.assertIsNone(ring_path) self.assertEqual(args, ['account', 'container', 'junk/test.ring.gz']) # -P two a c o hooya - options = Namespace(policy_name='two', partition=None) + options = Namespace(policy_name='two', partition=None, quoted=None) args = 'a c o hooya' self.assertRaisesMessage(InfoSystemExit, 'Invalid arguments', parse_get_node_args, options, args.split()) # -P zero -p 1 - options = Namespace(policy_name='zero', partition='1') + options = Namespace(policy_name='zero', partition='1', quoted=None) args = '' ring_path, args = parse_get_node_args(options, args.split()) self.assertIsNone(ring_path) self.assertFalse(args) # -P one -p 1 a/c/o - options = Namespace(policy_name='one', partition='1') + options = Namespace(policy_name='one', partition='1', quoted=None) args = 'a/c/o' ring_path, args = parse_get_node_args(options, args.split()) self.assertIsNone(ring_path) self.assertEqual(args, ['a', 'c', 'o']) # -P two -p 1 a c o hooya - options = Namespace(policy_name='two', partition='1') + options = Namespace(policy_name='two', partition='1', quoted=None) args = 'a c o hooya' self.assertRaisesMessage(InfoSystemExit, 'Invalid arguments', @@ -853,7 +841,7 @@ Shard Ranges (3): # Mock tests # /etc/swift/object.ring.gz(without any arguments i.e. a c o) - options = Namespace(policy_name=None, partition=None) + options = Namespace(policy_name=None, partition=None, quoted=None) args = '/etc/swift/object.ring.gz' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -863,7 +851,7 @@ Shard Ranges (3): parse_get_node_args, options, args.split()) # Similar ring_path and arguments # /etc/swift/object.ring.gz /etc/swift/object.ring.gz - options = Namespace(policy_name=None, partition=None) + options = Namespace(policy_name=None, partition=None, quoted=None) args = '/etc/swift/object.ring.gz /etc/swift/object.ring.gz' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -871,7 +859,7 @@ Shard Ranges (3): self.assertEqual(ring_path, '/etc/swift/object.ring.gz') self.assertEqual(args, ['etc', 'swift', 'object.ring.gz']) # /etc/swift/object.ring.gz a/c/etc/swift/object.ring.gz - options = Namespace(policy_name=None, partition=None) + options = Namespace(policy_name=None, partition=None, quoted=None) args = '/etc/swift/object.ring.gz a/c/etc/swift/object.ring.gz' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -880,7 +868,7 @@ Shard Ranges (3): self.assertEqual(args, ['a', 'c', 'etc/swift/object.ring.gz']) # Invalid path as mentioned in BUG#1539275 # /etc/swift/object.tar.gz account container object - options = Namespace(policy_name=None, partition=None) + options = Namespace(policy_name=None, partition=None, quoted=None) args = '/etc/swift/object.tar.gz account container object' self.assertRaisesMessage( InfoSystemExit, @@ -888,7 +876,7 @@ Shard Ranges (3): parse_get_node_args, options, args.split()) # object.ring.gz a/ - options = Namespace(policy_name=None) + options = Namespace(policy_name=None, quoted=None) args = 'object.ring.gz a/' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -896,7 +884,7 @@ Shard Ranges (3): self.assertEqual(ring_path, 'object.ring.gz') self.assertEqual(args, ['a']) # object.ring.gz a/c - options = Namespace(policy_name=None) + options = Namespace(policy_name=None, quoted=None) args = 'object.ring.gz a/c' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -904,7 +892,7 @@ Shard Ranges (3): self.assertEqual(ring_path, 'object.ring.gz') self.assertEqual(args, ['a', 'c']) # object.ring.gz a/c/o - options = Namespace(policy_name=None) + options = Namespace(policy_name=None, quoted=None) args = 'object.ring.gz a/c/o' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -912,7 +900,7 @@ Shard Ranges (3): self.assertEqual(ring_path, 'object.ring.gz') self.assertEqual(args, ['a', 'c', 'o']) # object.ring.gz a/c/o/junk/test.ring.gz - options = Namespace(policy_name=None) + options = Namespace(policy_name=None, quoted=None) args = 'object.ring.gz a/c/o/junk/test.ring.gz' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -920,7 +908,7 @@ Shard Ranges (3): self.assertEqual(ring_path, 'object.ring.gz') self.assertEqual(args, ['a', 'c', 'o/junk/test.ring.gz']) # object.ring.gz a - options = Namespace(policy_name=None) + options = Namespace(policy_name=None, quoted=None) args = 'object.ring.gz a' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -928,7 +916,7 @@ Shard Ranges (3): self.assertEqual(ring_path, 'object.ring.gz') self.assertEqual(args, ['a']) # object.ring.gz a c - options = Namespace(policy_name=None) + options = Namespace(policy_name=None, quoted=None) args = 'object.ring.gz a c' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -936,7 +924,7 @@ Shard Ranges (3): self.assertEqual(ring_path, 'object.ring.gz') self.assertEqual(args, ['a', 'c']) # object.ring.gz a c o - options = Namespace(policy_name=None) + options = Namespace(policy_name=None, quoted=None) args = 'object.ring.gz a c o' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -944,7 +932,7 @@ Shard Ranges (3): self.assertEqual(ring_path, 'object.ring.gz') self.assertEqual(args, ['a', 'c', 'o']) # object.ring.gz a c o blah blah - options = Namespace(policy_name=None) + options = Namespace(policy_name=None, quoted=None) args = 'object.ring.gz a c o blah blah' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -953,7 +941,7 @@ Shard Ranges (3): 'Invalid arguments', parse_get_node_args, options, args.split()) # object.ring.gz a/c/o/blah/blah - options = Namespace(policy_name=None) + options = Namespace(policy_name=None, quoted=None) args = 'object.ring.gz a/c/o/blah/blah' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -962,7 +950,7 @@ Shard Ranges (3): self.assertEqual(args, ['a', 'c', 'o/blah/blah']) # object.ring.gz -p 1 - options = Namespace(policy_name=None, partition='1') + options = Namespace(policy_name=None, partition='1', quoted=None) args = 'object.ring.gz' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -970,7 +958,7 @@ Shard Ranges (3): self.assertEqual(ring_path, 'object.ring.gz') self.assertFalse(args) # object.ring.gz -p 1 a c o - options = Namespace(policy_name=None, partition='1') + options = Namespace(policy_name=None, partition='1', quoted=None) args = 'object.ring.gz a c o' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -978,7 +966,7 @@ Shard Ranges (3): self.assertEqual(ring_path, 'object.ring.gz') self.assertEqual(args, ['a', 'c', 'o']) # object.ring.gz -p 1 a c o forth_arg - options = Namespace(policy_name=None, partition='1') + options = Namespace(policy_name=None, partition='1', quoted=None) args = 'object.ring.gz a c o forth_arg' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -987,7 +975,7 @@ Shard Ranges (3): 'Invalid arguments', parse_get_node_args, options, args.split()) # object.ring.gz -p 1 a/c/o - options = Namespace(policy_name=None, partition='1') + options = Namespace(policy_name=None, partition='1', quoted=None) args = 'object.ring.gz a/c/o' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -995,7 +983,7 @@ Shard Ranges (3): self.assertEqual(ring_path, 'object.ring.gz') self.assertEqual(args, ['a', 'c', 'o']) # object.ring.gz -p 1 a/c/junk/test.ring.gz - options = Namespace(policy_name=None, partition='1') + options = Namespace(policy_name=None, partition='1', quoted=None) args = 'object.ring.gz a/c/junk/test.ring.gz' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -1003,7 +991,7 @@ Shard Ranges (3): self.assertEqual(ring_path, 'object.ring.gz') self.assertEqual(args, ['a', 'c', 'junk/test.ring.gz']) # object.ring.gz -p 1 a/c/photos/cat.jpg - options = Namespace(policy_name=None, partition='1') + options = Namespace(policy_name=None, partition='1', quoted=None) args = 'object.ring.gz a/c/photos/cat.jpg' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -1012,7 +1000,7 @@ Shard Ranges (3): self.assertEqual(args, ['a', 'c', 'photos/cat.jpg']) # --all object.ring.gz a - options = Namespace(all=True, policy_name=None) + options = Namespace(all=True, policy_name=None, quoted=None) args = 'object.ring.gz a' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -1020,7 +1008,7 @@ Shard Ranges (3): self.assertEqual(ring_path, 'object.ring.gz') self.assertEqual(args, ['a']) # --all object.ring.gz a c - options = Namespace(all=True, policy_name=None) + options = Namespace(all=True, policy_name=None, quoted=None) args = 'object.ring.gz a c' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -1028,7 +1016,7 @@ Shard Ranges (3): self.assertEqual(ring_path, 'object.ring.gz') self.assertEqual(args, ['a', 'c']) # --all object.ring.gz a c o - options = Namespace(all=True, policy_name=None) + options = Namespace(all=True, policy_name=None, quoted=None) args = 'object.ring.gz a c o' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -1036,7 +1024,7 @@ Shard Ranges (3): self.assertEqual(ring_path, 'object.ring.gz') self.assertEqual(args, ['a', 'c', 'o']) # object.ring.gz account container photos/cat.jpg - options = Namespace(policy_name=None, partition=None) + options = Namespace(policy_name=None, partition=None, quoted=None) args = 'object.ring.gz account container photos/cat.jpg' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -1044,7 +1032,7 @@ Shard Ranges (3): self.assertEqual(ring_path, 'object.ring.gz') self.assertEqual(args, ['account', 'container', 'photos/cat.jpg']) # object.ring.gz /account/container/photos/cat.jpg - options = Namespace(policy_name=None, partition=None) + options = Namespace(policy_name=None, partition=None, quoted=None) args = 'object.ring.gz account/container/photos/cat.jpg' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -1053,7 +1041,7 @@ Shard Ranges (3): self.assertEqual(args, ['account', 'container', 'photos/cat.jpg']) # Object name ends with 'ring.gz' # object.ring.gz /account/container/junk/test.ring.gz - options = Namespace(policy_name=None, partition=None) + options = Namespace(policy_name=None, partition=None, quoted=None) args = 'object.ring.gz account/container/junk/test.ring.gz' with mock.patch('swift.cli.info.os.path.exists') as exists: exists.return_value = True @@ -1061,6 +1049,32 @@ Shard Ranges (3): self.assertEqual(ring_path, 'object.ring.gz') self.assertEqual(args, ['account', 'container', 'junk/test.ring.gz']) + # Object name has special characters + # object.ring.gz /account/container/obj\nwith%0anewline + options = Namespace(policy_name=None, partition=None, quoted=None) + args = ['object.ring.gz', 'account/container/obj\nwith%0anewline'] + with mock.patch('swift.cli.info.os.path.exists') as exists: + exists.return_value = True + ring_path, args = parse_get_node_args(options, args) + self.assertEqual(ring_path, 'object.ring.gz') + self.assertEqual(args, ['account', 'container', 'obj\nwith%0anewline']) + + options = Namespace(policy_name=None, partition=None, quoted=True) + args = ['object.ring.gz', 'account/container/obj\nwith%0anewline'] + with mock.patch('swift.cli.info.os.path.exists') as exists: + exists.return_value = True + ring_path, args = parse_get_node_args(options, args) + self.assertEqual(ring_path, 'object.ring.gz') + self.assertEqual(args, ['account', 'container', 'obj\nwith\nnewline']) + + options = Namespace(policy_name=None, partition=None, quoted=False) + args = ['object.ring.gz', 'account/container/obj\nwith%0anewline'] + with mock.patch('swift.cli.info.os.path.exists') as exists: + exists.return_value = True + ring_path, args = parse_get_node_args(options, args) + self.assertEqual(ring_path, 'object.ring.gz') + self.assertEqual(args, ['account', 'container', 'obj\nwith%0anewline']) + class TestPrintObj(TestCliInfoBase):