Repository: deis/deis

Issue:
Default Formation
As a developer deploying apps to Deis I would like to have default formation so that I don't have to specify the formation id for CLI commands.

This could be set by an admin at the controller level and / or a developer preference at the user account level.

Additionally, if there is only one formation it should be the default.



Discussion:
+1.  If someone's looking to contribute to Deis core, this should be fairly easy to implement:
- Change CLI to make formation an optional field
- Change API endpoint to lookup a default formation, and fail with a 400 (and nice error) if one doesn't exist

+1


Resolving patch:
diff --git a/api/serializers.py b/api/serializers.py
--- a/api/serializers.py
+++ b/api/serializers.py
@@ -160,7 +160,7 @@ class AppSerializer(serializers.ModelSerializer):
 
     owner = serializers.Field(source='owner.username')
     id = serializers.SlugField(default=utils.generate_app_name)
-    formation = OwnerSlugRelatedField(slug_field='id')
+    formation = OwnerSlugRelatedField(slug_field='id', required=False)
 
     class Meta:
         """Metadata options for a :class:`AppSerializer`."""
diff --git a/api/views.py b/api/views.py
--- a/api/views.py
+++ b/api/views.py
@@ -308,6 +308,16 @@ def pre_save(self, app, created=False, **kwargs):
         return super(AppViewSet, self).pre_save(app, **kwargs)
 
     def create(self, request, **kwargs):
+        if not 'formation' in request.DATA:
+            count = models.Formation.objects.count()
+            if count == 1:
+                request.DATA['formation'] = models.Formation.objects.first()
+            elif count == 0:
+                return Response('No formations available',
+                                status=HTTP_400_BAD_REQUEST)
+            else:
+                return Response('Could not determine default formation',
+                                status=HTTP_400_BAD_REQUEST)
         try:
             return OwnerViewSet.create(self, request, **kwargs)
         except EnvironmentError as e:
diff --git a/client/deis.py b/client/deis.py
--- a/client/deis.py
+++ b/client/deis.py
@@ -391,11 +391,10 @@ def apps_create(self, args):
         """
         Create a new application
 
-        Must provide a target formation to host the application
-        containers. If no ID is provided, one will be generated
-        automatically.
+        If no ID is provided, one will be generated automatically.
+        If no formation is provided, the first available will be used.
 
-        Usage: deis apps:create --formation=<formation> [--id=<id>]
+        Usage: deis apps:create [--id=<id> --formation=<formation>]
         """
         body = {}
         try:
@@ -413,11 +412,6 @@ def apps_create(self, args):
             o = args.get(opt)
             if o:
                 body.update({opt.strip('-'): o})
-        formation = args.get('--formation')
-        response = self._dispatch('get', '/api/formations/{}'.format(formation))
-        if response.status_code != 200:
-            print('Formation not found')
-            return
         sys.stdout.write('Creating application... ')
         sys.stdout.flush()
         try:


Test patch:
diff --git a/api/tests/test_app.py b/api/tests/test_app.py
--- a/api/tests/test_app.py
+++ b/api/tests/test_app.py
@@ -111,6 +111,34 @@ def test_app_override_id(self):
         self.assertContains(response, 'App with this Id already exists.', status_code=400)
         return response
 
+    def test_app_default_formation(self):
+        # delete formation created in setUp
+        response = self.client.delete('/api/formations/autotest')
+        self.assertEqual(response.status_code, 204)
+        # try creating an app with no formation specified
+        url = '/api/apps'
+        response = self.client.post(url)
+        self.assertContains(response, 'No formations available', status_code=400)
+        # create a formation
+        formation1 = 'autotest'
+        response = self.client.post('/api/formations', json.dumps({'id': formation1}),
+                                    content_type='application/json')
+        self.assertEqual(response.status_code, 201)
+        # try again to create an app with no formation specified
+        url = '/api/apps'
+        response = self.client.post(url)
+        self.assertEqual(response.status_code, 201)
+        self.assertEqual(formation1, response.data['formation'])
+        # create a second formation
+        formation2 = 'autotest2'
+        response = self.client.post('/api/formations', json.dumps({'id': formation2}),
+                                    content_type='application/json')
+        self.assertEqual(response.status_code, 201)
+        # create another app with no formation specified
+        url = '/api/apps'
+        response = self.client.post(url)
+        self.assertContains(response, 'Could not determine default formation', status_code=400)
+
     def test_multiple_apps(self):
         url = '/api/apps'
         body = {'formation': 'autotest'}