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
```

Discussion:

Patch:
diff --git a/conductr_cli/__init__.py b/conductr_cli/__init__.py
index 0502704..3e7f920 100644
--- a/conductr_cli/__init__.py
+++ b/conductr_cli/__init__.py
@@ -1,1 +1,1 @@
-__version__ = '1.2.20'
+__version__ = '1.2.19'
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/license.py b/conductr_cli/license.py
index e707834..debfd53 100644
--- a/conductr_cli/license.py
+++ b/conductr_cli/license.py
@@ -3,6 +3,7 @@ from conductr_cli.conduct_url import conductr_host
 from conductr_cli.exceptions import LicenseDownloadError
 from dcos.errors import DCOSHTTPException
 import arrow
+import base64
 import datetime
 import json
 import logging
@@ -32,7 +33,10 @@ def download_license(args, save_to, use_cached_auth_token):
     if not auth_token:
         auth_token = license_auth.prompt_for_auth_token()
 
-    auth_header = {'Authorization': 'Bearer {}'.format(auth_token)}
+    auth_token_b64_bytes = base64.b64encode(bytes(auth_token, 'UTF-8'))
+    auth_token_b64 = auth_token_b64_bytes.decode('UTF-8')
+
+    auth_header = {'Authorization': 'Bearer {}'.format(auth_token_b64)}
     response = requests.get(args.license_download_url,
                             headers=auth_header,
                             verify=args.server_verification_file)


Test patch:
diff --git a/conductr_cli/test/test_license.py b/conductr_cli/test/test_license.py
index 92f8acf..f27911c 100644
--- a/conductr_cli/test/test_license.py
+++ b/conductr_cli/test/test_license.py
@@ -14,6 +14,7 @@ import tempfile
 
 class TestDownloadLicense(CliTestCase):
     cached_token = 'test-token'
+    cached_token_b64 = 'dGVzdC10b2tlbg=='
     license_text = 'test-license-text'
     license_download_url = 'http://test.com/download'
     server_verification_file = 'test-server_verification_file'
@@ -47,7 +48,7 @@ class TestDownloadLicense(CliTestCase):
         mock_get_cached_auth_token.assert_called_once_with()
         mock_prompt_for_auth_token.assert_not_called()
         mock_get.assert_called_once_with(self.license_download_url,
-                                         headers={'Authorization': 'Bearer {}'.format(self.cached_token)},
+                                         headers={'Authorization': 'Bearer {}'.format(self.cached_token_b64)},
                                          verify=self.server_verification_file)
         mock_save_auth_token.assert_called_once_with(self.cached_token)
         mock_save_license_data.assert_called_once_with(self.license_text, self.license_file)
@@ -57,6 +58,7 @@ class TestDownloadLicense(CliTestCase):
         mock_get_cached_auth_token = MagicMock(return_value=None)
 
         prompted_token = 'prompted-token'
+        prompted_token_b64 = 'cHJvbXB0ZWQtdG9rZW4='
         mock_prompt_for_auth_token = MagicMock(return_value=prompted_token)
 
         mock_get = self.respond_with(200, self.license_text)
@@ -76,7 +78,7 @@ class TestDownloadLicense(CliTestCase):
         mock_get_cached_auth_token.assert_called_once_with()
         mock_prompt_for_auth_token.assert_called_once_with()
         mock_get.assert_called_once_with(self.license_download_url,
-                                         headers={'Authorization': 'Bearer {}'.format(prompted_token)},
+                                         headers={'Authorization': 'Bearer {}'.format(prompted_token_b64)},
                                          verify=self.server_verification_file)
         mock_save_auth_token.assert_called_once_with(prompted_token)
         mock_save_license_data.assert_called_once_with(self.license_text, self.license_file)
@@ -102,7 +104,7 @@ class TestDownloadLicense(CliTestCase):
         mock_get_cached_auth_token.assert_called_once_with()
         mock_prompt_for_auth_token.assert_not_called()
         mock_get.assert_called_once_with(self.license_download_url,
-                                         headers={'Authorization': 'Bearer {}'.format(self.cached_token)},
+                                         headers={'Authorization': 'Bearer {}'.format(self.cached_token_b64)},
                                          verify=self.server_verification_file)
         mock_save_auth_token.assert_not_called()
         mock_save_license_data.assert_not_called()
@@ -128,7 +130,7 @@ class TestDownloadLicense(CliTestCase):
         mock_get_cached_auth_token.assert_called_once_with()
         mock_prompt_for_auth_token.assert_not_called()
         mock_get.assert_called_once_with(self.license_download_url,
-                                         headers={'Authorization': 'Bearer {}'.format(self.cached_token)},
+                                         headers={'Authorization': 'Bearer {}'.format(self.cached_token_b64)},
                                          verify=self.server_verification_file)
         mock_save_auth_token.assert_not_called()
         mock_save_license_data.assert_not_called()
@@ -138,6 +140,7 @@ class TestDownloadLicense(CliTestCase):
         mock_get_cached_auth_token = MagicMock(return_value=None)
 
         prompted_token = 'prompted-token'
+        prompted_token_b64 = 'cHJvbXB0ZWQtdG9rZW4='
         mock_prompt_for_auth_token = MagicMock(return_value=prompted_token)
 
         mock_get = self.respond_with(200, self.license_text)
@@ -157,7 +160,7 @@ class TestDownloadLicense(CliTestCase):
         mock_get_cached_auth_token.assert_not_called()
         mock_prompt_for_auth_token.assert_called_once_with()
         mock_get.assert_called_once_with(self.license_download_url,
-                                         headers={'Authorization': 'Bearer {}'.format(prompted_token)},
+                                         headers={'Authorization': 'Bearer {}'.format(prompted_token_b64)},
                                          verify=self.server_verification_file)
         mock_save_auth_token.assert_called_once_with(prompted_token)
         mock_save_license_data.assert_called_once_with(self.license_text, self.license_file)