Repository: typesafehub/conductr-cli

Issue:
`conduct backup` prints to TTY
When using `conduct backup`, it should do a TTY check similar to this to ensure that we don't print "garbage" to the TTY:

https://github.com/typesafehub/conductr-cli/blob/master/conductr_cli/bndl_main.py#L34-L36

So I'd expect behavior like this:

Failure:
```bash
$ conduct backup
conduct: Refusing to write to terminal. Provide -o or redirect elsewhere
-> 2
```

Success:
```bash
$ conduct backup > my-file
-> 0
```

Comments:

Patch:
diff --git a/conductr_cli/conductr_backup.py b/conductr_cli/conductr_backup.py
index e8d3d3f..04b515d 100644
--- a/conductr_cli/conductr_backup.py
+++ b/conductr_cli/conductr_backup.py
@@ -61,12 +61,18 @@ def backup(args):
             backup_agents(args, backup_directory)
 
             compress_backup(args.output_path, backup_directory)
-
     finally:
         remove_backup_directory(backup_directory)
+    return True
 
 
 def compress_backup(output_path, backup_directory):
+    log = logging.getLogger(__name__)
+
+    if sys.stdout.isatty() and output_path is None:
+        log.error('conduct backup: Refusing to write to terminal. Provide -o or redirect elsewhere')
+        sys.exit(2)
+
     output_file = open(output_path, 'wb') if output_path else sys.stdout.buffer
     with tempfile.NamedTemporaryFile() as zip_file_data:
         with zipfile.ZipFile(zip_file_data, 'w') as zip_file:
diff --git a/conductr_cli/host.py b/conductr_cli/host.py
index e7974af..e50f1a7 100644
--- a/conductr_cli/host.py
+++ b/conductr_cli/host.py
@@ -83,6 +83,15 @@ def is_listening(ip_addr, port):
     return result
 
 
+def alias_number(addr):
+    if addr.version == 4:
+        return int(addr.exploded.split('.')[-1], 10) - 1
+    elif addr.version == 6:
+        return int(addr.exploded.split(':')[-1], 16) - 1
+    else:
+        raise ValueError('version must be 4 or 6, given {}'.format(addr.version))
+
+
 def addr_alias_commands(addrs, ip_version):
     if_name = loopback_device_name()
 
@@ -90,7 +99,7 @@ def addr_alias_commands(addrs, ip_version):
 
     commands = []
     if is_linux():
-        commands = [['sudo', 'ifconfig', '{}:{}'.format(if_name, int(addr.exploded[-1:]) - 1),
+        commands = [['sudo', 'ifconfig', '{}:{}'.format(if_name, alias_number(addr)),
                      addr.exploded, 'netmask', subnet_mask, 'up'] for addr in addrs]
     elif is_macos():
         commands = [['sudo', 'ifconfig', if_name, 'alias', addr.exploded, subnet_mask] for addr in addrs]
diff --git a/conductr_cli/terminal.py b/conductr_cli/terminal.py
index 2c27df4..84205cb 100644
--- a/conductr_cli/terminal.py
+++ b/conductr_cli/terminal.py
@@ -28,7 +28,9 @@ def docker_inspect(container_id, inspect_format=None):
 
 def docker_run(optional_args, image, positional_args):
     cmd = ['docker', 'run'] + optional_args + [image] + positional_args
-    return subprocess.call(cmd)
+    status = subprocess.call(cmd)
+    assert status == 0, 'docker exited with {}'.format(status)
+    return 0
 
 
 def docker_rm(containers):


Test patch:
diff --git a/conductr_cli/test/test_host.py b/conductr_cli/test/test_host.py
index 380225c..2a9aef8 100644
--- a/conductr_cli/test/test_host.py
+++ b/conductr_cli/test/test_host.py
@@ -254,6 +254,51 @@ class TestAddrAliasCommands(TestCase):
         mock_is_linux.assert_called_once_with()
         mock_is_macos.assert_not_called()
 
+    def test_linux_ipv4_large(self):
+        mock_loopback_device_name = MagicMock(return_value=self.loopback_device_name)
+        mock_is_linux = MagicMock(return_value=True)
+        mock_is_macos = MagicMock(return_value=False)
+
+        with patch('conductr_cli.host.loopback_device_name', mock_loopback_device_name), \
+                patch('conductr_cli.host.is_linux', mock_is_linux), \
+                patch('conductr_cli.host.is_macos', mock_is_macos):
+            result = host.addr_alias_commands([
+                ipaddress.ip_address('192.168.1.1'),
+                ipaddress.ip_address('192.168.1.2'),
+                ipaddress.ip_address('192.168.1.3'),
+                ipaddress.ip_address('192.168.1.4'),
+                ipaddress.ip_address('192.168.1.5'),
+                ipaddress.ip_address('192.168.1.6'),
+                ipaddress.ip_address('192.168.1.7'),
+                ipaddress.ip_address('192.168.1.8'),
+                ipaddress.ip_address('192.168.1.9'),
+                ipaddress.ip_address('192.168.1.10'),
+                ipaddress.ip_address('192.168.1.11'),
+                ipaddress.ip_address('192.168.1.12'),
+                ipaddress.ip_address('192.168.1.13')
+            ], 4)
+
+            expected_result = [
+                ['sudo', 'ifconfig', 'ix0:0', '192.168.1.1', 'netmask', '255.255.255.255', 'up'],
+                ['sudo', 'ifconfig', 'ix0:1', '192.168.1.2', 'netmask', '255.255.255.255', 'up'],
+                ['sudo', 'ifconfig', 'ix0:2', '192.168.1.3', 'netmask', '255.255.255.255', 'up'],
+                ['sudo', 'ifconfig', 'ix0:3', '192.168.1.4', 'netmask', '255.255.255.255', 'up'],
+                ['sudo', 'ifconfig', 'ix0:4', '192.168.1.5', 'netmask', '255.255.255.255', 'up'],
+                ['sudo', 'ifconfig', 'ix0:5', '192.168.1.6', 'netmask', '255.255.255.255', 'up'],
+                ['sudo', 'ifconfig', 'ix0:6', '192.168.1.7', 'netmask', '255.255.255.255', 'up'],
+                ['sudo', 'ifconfig', 'ix0:7', '192.168.1.8', 'netmask', '255.255.255.255', 'up'],
+                ['sudo', 'ifconfig', 'ix0:8', '192.168.1.9', 'netmask', '255.255.255.255', 'up'],
+                ['sudo', 'ifconfig', 'ix0:9', '192.168.1.10', 'netmask', '255.255.255.255', 'up'],
+                ['sudo', 'ifconfig', 'ix0:10', '192.168.1.11', 'netmask', '255.255.255.255', 'up'],
+                ['sudo', 'ifconfig', 'ix0:11', '192.168.1.12', 'netmask', '255.255.255.255', 'up'],
+                ['sudo', 'ifconfig', 'ix0:12', '192.168.1.13', 'netmask', '255.255.255.255', 'up']
+            ]
+            self.assertEqual(expected_result, result)
+
+        mock_loopback_device_name.assert_called_once_with()
+        mock_is_linux.assert_called_once_with()
+        mock_is_macos.assert_not_called()
+
     def test_linux_ipv6(self):
         mock_loopback_device_name = MagicMock(return_value=self.loopback_device_name)
         mock_is_linux = MagicMock(return_value=True)
@@ -265,8 +310,8 @@ class TestAddrAliasCommands(TestCase):
             result = host.addr_alias_commands(self.addrs_ipv6, 6)
 
             expected_result = [
-                ['sudo', 'ifconfig', 'ix0:0', '0000:0000:0000:0000:0000:ffff:c0a8:0101', 'netmask', 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', 'up'],
-                ['sudo', 'ifconfig', 'ix0:1', '0000:0000:0000:0000:0000:ffff:c0a8:0102', 'netmask', 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', 'up']
+                ['sudo', 'ifconfig', 'ix0:256', '0000:0000:0000:0000:0000:ffff:c0a8:0101', 'netmask', 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', 'up'],
+                ['sudo', 'ifconfig', 'ix0:257', '0000:0000:0000:0000:0000:ffff:c0a8:0102', 'netmask', 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', 'up']
             ]
             self.assertEqual(expected_result, result)
 
diff --git a/conductr_cli/test/test_terminal.py b/conductr_cli/test/test_terminal.py
index d6ff783..0337492 100644
--- a/conductr_cli/test/test_terminal.py
+++ b/conductr_cli/test/test_terminal.py
@@ -72,7 +72,7 @@ class TestTerminal(CliTestCase):
         image = 'image:version'
         positional_args = ['--discover-host-ip']
         stdout = MagicMock()
-        subprocess_call_mock = MagicMock()
+        subprocess_call_mock = MagicMock(return_value=0)
 
         with patch('subprocess.call', subprocess_call_mock), \
                 patch('sys.stdout', stdout):
@@ -81,6 +81,21 @@ class TestTerminal(CliTestCase):
         self.assertEqual('', self.output(stdout))
         subprocess_call_mock.assert_called_with(['docker', 'run'] + optional_args + [image] + positional_args)
 
+    def test_docker_run_fail(self):
+        optional_args = ['-p', '9001:9001', '-e', 'AKKA_LOGLEVEL=info']
+        image = 'image:version'
+        positional_args = ['--discover-host-ip']
+        stdout = MagicMock()
+        subprocess_call_mock = MagicMock(return_value=1)
+
+        with patch('subprocess.call', subprocess_call_mock), \
+                patch('sys.stdout', stdout), \
+                self.assertRaises(AssertionError):
+            terminal.docker_run(optional_args, image, positional_args)
+
+        self.assertEqual('', self.output(stdout))
+        subprocess_call_mock.assert_called_with(['docker', 'run'] + optional_args + [image] + positional_args)
+
     def test_docker_rm(self):
         containers = ['cond-0', 'cond-1']
         stdout = MagicMock()