Repository: deis/deis

Issue:
Overriding ID during `deis create` results in client 404's
```
(deis)ben$ example-python-flask > deis create --formation=pythonFormation --id=pythonApp
Creating application... done, created pythonApp
Git remote deis added
(deis)ben$ example-python-flask > git push deis master
Counting objects: 62, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (37/37), done.
Writing objects: 100% (62/62), 12.98 KiB, done.
Total 62 (delta 18), reused 62 (delta 18)
       Python app detected
-----> No runtime.txt provided; assuming python-2.7.4.
-----> Preparing Python runtime (python-2.7.4)
-----> Installing Distribute (0.6.36)
-----> Installing Pip (1.3.1)
-----> Installing dependencies using Pip (1.3.1)
       Downloading/unpacking Flask==0.9 (from -r requirements.txt (line 1))
         Running setup.py egg_info for package Flask

           warning: no files found matching '*' under directory 'tests'
           no previously-included directories found matching 'docs/_build'
           no previously-included directories found matching 'docs/_themes/.git'
       Downloading/unpacking Jinja2==2.6 (from -r requirements.txt (line 2))
         Running setup.py egg_info for package Jinja2

       Downloading/unpacking gunicorn==0.17.2 (from -r requirements.txt (line 3))
         Running setup.py egg_info for package gunicorn

       Downloading/unpacking Werkzeug>=0.7 (from Flask==0.9->-r requirements.txt (line 1))
         Running setup.py egg_info for package Werkzeug

           warning: no files found matching '*' under directory 'werkzeug/debug/templates'
           warning: no files found matching '*' under directory 'tests'
           no previously-included directories found matching 'docs/_build'
       Installing collected packages: Flask, Jinja2, gunicorn, Werkzeug
         Running setup.py install for Flask

           warning: no files found matching '*' under directory 'tests'
           no previously-included directories found matching 'docs/_build'
           no previously-included directories found matching 'docs/_themes/.git'
         Running setup.py install for Jinja2

         Running setup.py install for gunicorn

           Installing gunicorn_paster script to /app/.heroku/python/bin
           Installing gunicorn script to /app/.heroku/python/bin
           Installing gunicorn_django script to /app/.heroku/python/bin
         Running setup.py install for Werkzeug

           warning: no files found matching '*' under directory 'werkzeug/debug/templates'
           warning: no files found matching '*' under directory 'tests'
           no previously-included directories found matching 'docs/_build'
       Successfully installed Flask Jinja2 gunicorn Werkzeug
       Cleaning up...
-----> Discovering process types
       Procfile declares types -> web

-----> Compiled slug size: 28.5 MB
       Launching... done, v2

-----> pythonApp deployed to Deis
       http://ec2-54-200-220-59.us-west-2.compute.amazonaws.com

       To learn more, use `deis help` or visit http://deis.io

To git@ben.deis.io:pythonApp.git
 * [new branch]      master -> master
(deis)ben$ example-python-flask > deis open
404 NOT FOUND
{u'detail': u'Not found'}
(deis)ben$ example-python-flask > deis info
404 NOT FOUND
{u'detail': u'Not found'}
(deis)ben$ example-python-flask > deis run ls -la
404 NOT FOUND
{u'detail': u'Not found'}
```



Discussion:

Resolving patch:
diff --git a/api/serializers.py b/api/serializers.py
--- a/api/serializers.py
+++ b/api/serializers.py
@@ -5,6 +5,8 @@
 
 from __future__ import unicode_literals
 
+import re
+
 from django.contrib.auth.models import User
 from rest_framework import serializers
 
@@ -165,6 +167,16 @@ class Meta:
         model = models.App
         read_only_fields = ('created', 'updated')
 
+    def validate_id(self, attrs, source):
+        """
+        Check that the ID is all lowercase
+        """
+        value = attrs[source]
+        match = re.match(r'^[a-z0-9-]+$', value)
+        if not match:
+            raise serializers.ValidationError("App IDs can only contain [a-z0-9-]")
+        return attrs
+
 
 class ContainerSerializer(serializers.ModelSerializer):
     """Serialize a :class:`~api.models.Container` model."""
diff --git a/client/deis.py b/client/deis.py
--- a/client/deis.py
+++ b/client/deis.py
@@ -399,11 +399,11 @@ def apps_create(self, args):
             progress.join()
         if response.status_code == requests.codes.created:  # @UndefinedVariable
             data = response.json()
-            formation = data['id']
-            print("done, created {}".format(formation))
+            app_id = data['id']
+            print("done, created {}".format(app_id))
             # add a git remote
             hostname = urlparse.urlparse(self._settings['controller']).netloc
-            git_remote = "git@{hostname}:{formation}.git".format(**locals())
+            git_remote = "git@{hostname}:{app_id}.git".format(**locals())
             try:
                 subprocess.check_call(
                     ['git', 'remote', 'add', '-f', 'deis', git_remote],


Test patch:
diff --git a/api/tests/app.py b/api/tests/app.py
--- a/api/tests/app.py
+++ b/api/tests/app.py
@@ -185,6 +185,9 @@ def test_app_actions(self):
     def test_app_errors(self):
         formation_id, app_id = 'autotest', 'autotest-errors'
         url = '/api/apps'
+        body = {'formation': formation_id, 'id': 'camelCase'}
+        response = self.client.post(url, json.dumps(body), content_type='application/json')
+        self.assertContains(response, 'App IDs can only contain [a-z0-9-]', status_code=400)
         body = {'formation': formation_id, 'id': app_id}
         response = self.client.post(url, json.dumps(body), content_type='application/json')
         self.assertEqual(response.status_code, 201)