Repository: deis/deis

Issue:
Deis client is returning always return code 0
The deis client is returning for all commands the return code 0. This makes it difficult to use the client in a script.

This example should return 1 instead of 0.

```
$ deis run rake db:migrate
Warning: non-zero return code 1
Warning: '-rm' is deprecated, it will be removed soon. See usage.
Unable to find image 'deis/slugbuilder:v2' locally
Pulling repository deis/slugbuilder
2014/04/17 19:52:06 Tag v2 not found in repository deis/slugbuilder
$ echo $?
0
```



Discussion:
In terms of `deis run`, I think it should return the exit code of the process it's trying to run. Not sure about the other commands, but most of them return `system.exit(1)` like so: https://github.com/opdemand/deis/blob/master/client/deis.py#L431

@bacongobbler agreed.  We have the return code here: https://github.com/deis/deis/blob/master/client/deis.py#L609 so it should be easy to use it as the exit code.

Added easy-fix label. @johanneswuerbach if you (or anyone else) want to take a crack at a PR, it doesn't get much simpler than this! :)

We should probably audit all of our return conditions for client methods. For example: https://github.com/deis/deis/blob/master/client/deis.py#L641

Returning False doesn't seem to do anything? We should be raising Errors which are then interpreted in `_dispatch_cmd` and proper exit codes are issued. It seems like we currently have many ways to exit instead of just two (successful completion or proper reporting of an error condition).


Resolving patch:
diff --git a/client/deis.py b/client/deis.py
--- a/client/deis.py
+++ b/client/deis.py
@@ -268,37 +268,6 @@ def dictify(args):
     return data
 
 
-def get_provider_creds(provider, raise_error=False):
-    """Query environment variables and return a provider's creds if found.
-    """
-    cred_types = {
-        'ec2': [[('AWS_ACCESS_KEY_ID', 'access_key', None),
-                 ('AWS_SECRET_ACCESS_KEY', 'secret_key', None)],
-                [('AWS_ACCESS_KEY', 'access_key', None),
-                 ('AWS_SECRET_KEY', 'secret_key', None)]],
-        'rackspace': [[('RACKSPACE_USERNAME', 'username', None),
-                       ('RACKSPACE_API_KEY', 'api_key', None),
-                       ('CLOUD_ID_TYPE', 'identity_type', 'rackspace')]],
-        'digitalocean': [[('DIGITALOCEAN_CLIENT_ID', 'client_id', None),
-                          ('DIGITALOCEAN_API_KEY', 'api_key', None)]]
-    }
-    missing = None
-    for cred_set in cred_types[provider]:
-        creds = {}
-        for envvar, key, default in cred_set:
-            val = os.environ.get(envvar, default)
-            if not val:
-                missing = envvar
-                break
-            else:
-                creds[key] = val
-        if creds:
-            return creds
-    if raise_error:
-        raise EnvironmentError(
-            "Missing environment variable: {}".format(missing))
-
-
 def readable_datetime(datetime_str):
     """
     Return a human-readable datetime string from an ECMA-262 (JavaScript)
@@ -469,6 +438,7 @@ def apps_create(self, args):
                         stdout=subprocess.PIPE)
                     print('Git remote deis added')
                 except subprocess.CalledProcessError:
+                    print('Could not create Deis remote')
                     sys.exit(1)
         else:
             raise ResponseError(response)
@@ -607,13 +577,25 @@ def apps_run(self, args):
                                   json.dumps(body))
         if response.status_code == requests.codes.ok:  # @UndefinedVariable
             rc, output = json.loads(response.content)
-            if rc != 0:
-                print('Warning: non-zero return code {}'.format(rc))
             sys.stdout.write(output)
             sys.stdout.flush()
+            sys.exit(rc)
         else:
             raise ResponseError(response)
 
+    def auth(self, args):
+        """
+        Valid commands for auth:
+
+        auth:register          register a new user
+        auth:cancel            remove the current account
+        auth:login             authenticate against a controller
+        auth:logout            clear the current user session
+
+        Use `deis help [command]` to learn more
+        """
+        return
+
     def auth_register(self, args):
         """
         Register a new user with a Deis controller
@@ -638,7 +620,7 @@ def auth_register(self, args):
             confirm = getpass('password (confirm): ')
             if password != confirm:
                 print('Password mismatch, aborting registration.')
-                return False
+                sys.exit(1)
         email = args.get('--email')
         if not email:
             email = raw_input('email: ')
@@ -712,10 +694,9 @@ def auth_login(self, args):
             print("Logged in as {}".format(username))
             return username
         else:
-            print('Login failed')
             self._session.cookies.clear()
             self._session.cookies.save()
-            return False
+            raise ResponseError(response)
 
     def auth_logout(self, args):
         """
@@ -1176,7 +1157,7 @@ def _parse_key(self, path):
             match = re.match(r'^(ssh-...) ([^ ]+) ?(.*)', data)
             if not match:
                 print("Could not parse SSH public key {0}".format(name))
-                return
+                sys.exit(1)
             key_type, key_str, key_comment = match.groups()
             if key_comment:
                 key_id = key_comment
@@ -1327,151 +1308,6 @@ def _parse_perms_args(self, args):
             url = "/api/apps/{}/perms".format(app)
         return app, url
 
-    def providers(self, args):
-        """
-        Valid commands for providers:
-
-        providers:list        list available providers for the logged in user
-        providers:discover    discover provider credentials using envvars
-        providers:create      create a new provider for use by deis
-        providers:info        print information about a specific provider
-
-        Use `deis help [command]` to learn more
-        """
-        return self.providers_list(args)
-
-    def providers_create(self, args):  # noqa
-        """
-        Create a provider for use by Deis
-
-        This command is only necessary when adding a duplicate set of
-        credentials for a provider. User accounts start with empty providers,
-        EC2, Rackspace, and DigitalOcean by default, which should be updated
-        in place.
-
-        Use `providers:discover` to update the credentials for the default
-        providers created with your account.
-
-        Usage: deis providers:create <id> <type> <creds>
-        """
-        type = args.get('<type>')  # @ReservedAssignment
-        if type in ['ec2', 'rackspace', 'digitalocean']:
-            creds = get_provider_creds(type, raise_error=True)
-        else:
-            creds = json.loads(args.get('<creds>'))
-        id = args.get('<id>')  # @ReservedAssignment
-        if not id:
-            id = type  # @ReservedAssignment
-        body = {'id': id, 'type': type, 'creds': json.dumps(creds)}
-        response = self._dispatch('post', '/api/providers',
-                                  json.dumps(body))
-        if response.status_code == requests.codes.created:  # @UndefinedVariable
-            print("{0[id]}".format(response.json()))
-        else:
-            raise ResponseError(response)
-
-    def providers_discover(self, args):  # noqa
-        """
-        Discover and update provider credentials
-
-        This command will discover provider credentials using
-        standard environment variables like AWS_ACCESS_KEY and
-        AWS_SECRET_KEY.  It will use those credentials to update
-        the existing provider record, allowing you to use
-        pre-installed node flavors.
-
-        Usage: deis providers:discover
-        """
-        provider_data = [
-            # Provider, human-redable provider name, sample field to display
-            ('ec2', 'EC2', 'access_key'),
-            ('rackspace', 'Rackspace', 'api_key'),
-            ('digitalocean', 'DigitalOcean', 'api_key'),
-        ]
-        for provider, name, field in provider_data:
-            creds = get_provider_creds(provider)
-            if creds:
-                print ("Discovered {} credentials: {}".format(name, creds[field]))
-                inp = raw_input("Import {} credentials? (y/n) : ".format(name))
-                if inp.lower().strip('\n') != 'y':
-                    print('Aborting.')
-                else:
-                    body = {'creds': json.dumps(creds)}
-                    sys.stdout.write("Uploading {} credentials... ".format(name))
-                    sys.stdout.flush()
-                    endpoint = "/api/providers/{}".format(provider)
-                    response = self._dispatch('patch', endpoint, json.dumps(body))
-                    if response.status_code == requests.codes.ok:  # @UndefinedVariable
-                        print('done')
-                    else:
-                        raise ResponseError(response)
-            else:
-                print("No {} credentials discovered.".format(name))
-
-        # Check for locally booted Deis Controller VM
-        if '//deis-controller.local' in self._settings['controller']:
-            print("Discovered locally running Deis Controller VM")
-            # In order for the Controller to be able to boot Vagrant VMs it needs to run commands
-            # on the host machine. It does this via an SSH server. In order to access that server
-            # we need to send the current user's name and host.
-            try:
-                user = subprocess.check_output(
-                    "whoami",
-                    stderr=subprocess.PIPE
-                ).strip()
-            except subprocess.CalledProcessError:
-                print("Error detecting username.")
-                sys.exit(1)
-            creds = {
-                'user': user,
-                'host': '192.168.61.1'
-            }
-            body = {'creds': json.dumps(creds)}
-            sys.stdout.write('Activating Vagrant as a provider... ')
-            sys.stdout.flush()
-            response = self._dispatch('patch', '/api/providers/vagrant',
-                                      json.dumps(body))
-            if response.status_code == requests.codes.ok:  # @UndefinedVariable
-                print('done')
-            else:
-                raise ResponseError(response)
-        else:
-            print("No Vagrant Deis Controller discovered.")
-
-    def providers_info(self, args):
-        """
-        Print information about a specific provider
-
-        Usage: deis providers:info <provider>
-        """
-        provider = args.get('<provider>')
-        response = self._dispatch('get', "/api/providers/{}".format(provider))
-        if response.status_code == requests.codes.ok:  # @UndefinedVariable
-            print(json.dumps(response.json(), indent=2))
-        else:
-            raise ResponseError(response)
-
-    def providers_list(self, args):
-        """
-        List providers for the logged in user
-
-        Usage: deis providers:list
-        """
-        response = self._dispatch('get', '/api/providers')
-        if response.status_code == requests.codes.ok:  # @UndefinedVariable
-            data = response.json()
-            if data['count'] == 0:
-                print('No providers found')
-                return
-            print("=== {owner} Providers".format(**data['results'][0]))
-            for item in data['results']:
-                creds = json.loads(item['creds'])
-                if 'secret_key' in creds:
-                    creds.pop('secret_key')
-                print("{} => {}".format(item['id'], creds))
-        else:
-            raise ResponseError(response)
-
     def releases(self, args):
         """
         Valid commands for releases:


Test patch: