Repository: deis/deis

Issue:
App crashes when modifying an app's config with a different user
When you change the configuration of an app with a different user then last deploying user, the node converge will fail and the app goes offline.

Reproduce:
- Deploy app with user A
- Change the config of app with user B
- App is offline

If you use the same user for deployment and configuration it works as expected.



Discussion:
Nice catch. This seems to have to do with Deis failing to reconcile _usera/dockerimage_ with _userb/dockerimage_, when both are logically the same image for a globally unique app.

I'm working on a fix to switch the image being stored on the registry from `local.deisapp.com:5000/<user>/<appname>` to `local.deisapp.com:5000/<appname>`. That should better reflect the idea that an app can be shared between users and that the app names are unique across the cluster.


Resolving patch:
diff --git a/controller/api/exceptions.py b/controller/api/exceptions.py
--- a/controller/api/exceptions.py
+++ b/controller/api/exceptions.py
@@ -5,7 +5,6 @@
 from __future__ import unicode_literals
 
 from rest_framework.exceptions import APIException
-from rest_framework import status
 
 
 class AbstractDeisException(APIException):
@@ -22,10 +21,3 @@ def __init__(self, detail=None):
 
     class Meta:
         abstract = True
-
-
-class UserRegistrationException(AbstractDeisException):
-    """
-    Indicates that there was a problem registering the user.
-    """
-    status_code = status.HTTP_400_BAD_REQUEST
diff --git a/controller/api/models.py b/controller/api/models.py
--- a/controller/api/models.py
+++ b/controller/api/models.py
@@ -428,7 +428,7 @@ def new(self, user, config=None, build=None, summary=None):
             owner=user, app=self.app, config=config,
             build=build, version=new_version, image=image, summary=summary)
         # publish release to registry as new docker image
-        repository_path = "{}/{}".format(user.username, self.app.id)
+        repository_path = self.app.id
         publish_release(repository_path, config.values, tag)
         return release
 
diff --git a/controller/api/views.py b/controller/api/views.py
--- a/controller/api/views.py
+++ b/controller/api/views.py
@@ -22,7 +22,6 @@
 from rest_framework.response import Response
 
 from api import models, serializers
-from .exceptions import UserRegistrationException
 
 from django.conf import settings
 
@@ -147,9 +146,6 @@ def pre_save(self, obj):
         obj.is_active = True
         obj.email = User.objects.normalize_email(obj.email)
         obj.set_password(obj.password)
-        # FIXME: move this business logic to the model
-        if len(obj.username) < 4:
-            raise UserRegistrationException('username must be >= 4 characters in length')
         # Make this first signup an admin / superuser
         if not User.objects.filter(is_superuser=True).exists():
             obj.is_superuser = obj.is_staff = True


Test patch:
diff --git a/controller/api/tests/test_auth.py b/controller/api/tests/test_auth.py
--- a/controller/api/tests/test_auth.py
+++ b/controller/api/tests/test_auth.py
@@ -56,10 +56,6 @@ def test_auth(self):
         self.assertFalse(response.data['is_staff'])
         self.assertTrue(
             self.client.login(username=username, password=password))
-        # test with len(username) < 4
-        submit['username'] = 'new'
-        response = self.client.post(url, json.dumps(submit), content_type='application/json')
-        self.assertEqual(response.status_code, 400)
         # test logout and login
         url = '/api/auth/logout/'
         response = self.client.post(url, content_type='application/json')
diff --git a/controller/api/tests/test_hooks.py b/controller/api/tests/test_hooks.py
--- a/controller/api/tests/test_hooks.py
+++ b/controller/api/tests/test_hooks.py
@@ -106,7 +106,7 @@ def test_build_hook(self):
         url = '/api/hooks/builds'.format(**locals())
         body = {'receive_user': 'autotest',
                 'receive_repo': app_id,
-                'image': 'registry.local:5000/autotest/{app_id}:v2'.format(**locals())}
+                'image': 'registry.local:5000/{app_id}:v2'.format(**locals())}
         # post the build without a session
         self.assertIsNone(self.client.logout())
         response = self.client.post(url, json.dumps(body), content_type='application/json')