diff --git a/client/cmd/apps.go b/client/cmd/apps.go index aabac63ee1..968d1d8918 100644 --- a/client/cmd/apps.go +++ b/client/cmd/apps.go @@ -240,3 +240,24 @@ func AppDestroy(appID, confirm string) error { return nil } + +// AppTransfer transfers app ownership to another user. +func AppTransfer(appID, username string) error { + c, appID, err := load(appID) + + if err != nil { + return err + } + + fmt.Printf("Transferring %s to %s... ", appID, username) + + err = apps.Transfer(c, appID, username) + + if err != nil { + return err + } + + fmt.Println("done") + + return nil +} diff --git a/client/controller/api/apps.go b/client/controller/api/apps.go index 600a4696f9..89770e5a35 100644 --- a/client/controller/api/apps.go +++ b/client/controller/api/apps.go @@ -15,6 +15,11 @@ type AppCreateRequest struct { ID string `json:"id,omitempty"` } +// AppUpdateRequest is the definition of POST /v1/apps//. +type AppUpdateRequest struct { + Owner string `json:"owner,omitempty"` +} + // AppRunRequest is the definition of POST /v1/apps//run. type AppRunRequest struct { Command string `json:"command"` diff --git a/client/controller/models/apps/apps.go b/client/controller/models/apps/apps.go index a32fa4a55f..35720c26d6 100644 --- a/client/controller/models/apps/apps.go +++ b/client/controller/models/apps/apps.go @@ -123,3 +123,18 @@ func Delete(c *client.Client, appID string) error { _, err := c.BasicRequest("DELETE", u, nil) return err } + +// Transfer an app to another user. +func Transfer(c *client.Client, appID string, username string) error { + u := fmt.Sprintf("/v1/apps/%s/", appID) + + req := api.AppUpdateRequest{Owner: username} + body, err := json.Marshal(req) + + if err != nil { + return err + } + + _, err = c.BasicRequest("POST", u, body) + return err +} diff --git a/client/controller/models/apps/apps_test.go b/client/controller/models/apps/apps_test.go index 2506f21bc1..56f9553ea6 100644 --- a/client/controller/models/apps/apps_test.go +++ b/client/controller/models/apps/apps_test.go @@ -45,6 +45,7 @@ const appsFixture string = ` const appCreateExpected string = `{"id":"example-go"}` const appRunExpected string = `{"command":"echo hi"}` +const appTransferExpected string = `{"owner":"test"}` type fakeHTTPServer struct { createID bool @@ -127,6 +128,27 @@ func (f *fakeHTTPServer) ServeHTTP(res http.ResponseWriter, req *http.Request) { return } + if req.URL.Path == "/v1/apps/example-go/" && req.Method == "POST" { + body, err := ioutil.ReadAll(req.Body) + + if err != nil { + fmt.Println(err) + res.WriteHeader(http.StatusInternalServerError) + res.Write(nil) + } + + if string(body) != appTransferExpected { + fmt.Printf("Expected '%s', Got '%s'\n", appTransferExpected, body) + res.WriteHeader(http.StatusInternalServerError) + res.Write(nil) + return + } + + res.WriteHeader(http.StatusNoContent) + res.Write(nil) + return + } + fmt.Printf("Unrecongized URL %s\n", req.URL) res.WriteHeader(http.StatusNotFound) res.Write(nil) @@ -347,3 +369,25 @@ func TestAppsLogs(t *testing.T) { } } } + +func TestAppsTransfer(t *testing.T) { + t.Parallel() + + handler := fakeHTTPServer{} + server := httptest.NewServer(&handler) + defer server.Close() + + u, err := url.Parse(server.URL) + + if err != nil { + t.Fatal(err) + } + + httpClient := client.CreateHTTPClient(false) + + client := client.Client{HTTPClient: httpClient, ControllerURL: *u, Token: "abc"} + + if err = Transfer(&client, "example-go", "test"); err != nil { + t.Fatal(err) + } +} diff --git a/client/parser/apps.go b/client/parser/apps.go index e1531fdcc6..bdba94b3b5 100644 --- a/client/parser/apps.go +++ b/client/parser/apps.go @@ -8,7 +8,7 @@ import ( docopt "github.com/docopt/docopt-go" ) -// Apps routes app commands to the specific function +// Apps routes app commands to their specific function. func Apps(argv []string) error { usage := ` Valid commands for apps: @@ -20,6 +20,7 @@ apps:open open the application in a browser apps:logs view aggregated application logs apps:run run a command in an ephemeral app container apps:destroy destroy an application +apps:transfer transfer app ownership to another user Use 'deis help [command]' to learn more. ` @@ -39,6 +40,8 @@ Use 'deis help [command]' to learn more. return appRun(argv) case "apps:destroy": return appDestroy(argv) + case "apps:transfer": + return appTransfer(argv) default: if printHelp(argv, usage) { return nil @@ -244,3 +247,26 @@ Options: return cmd.AppDestroy(app, confirm) } + +func appTransfer(argv []string) error { + usage := ` +Transfer app ownership to another user. + +Usage: deis apps:transfer [options] + +Arguments: + + the user that the app will be transfered to. + +Options: + -a --app= + the uniquely identifiable name for the application. +` + args, err := docopt.Parse(usage, argv, true, "", false, true) + + if err != nil { + return err + } + + return cmd.AppTransfer(safeGetValue(args, "--app"), safeGetValue(args, "")) +} diff --git a/controller/api/__init__.py b/controller/api/__init__.py index 27e9b2bbc2..bda40ca767 100644 --- a/controller/api/__init__.py +++ b/controller/api/__init__.py @@ -2,4 +2,4 @@ The **api** Django app presents a RESTful web API for interacting with the **deis** system. """ -__version__ = '1.6.0' +__version__ = '1.7.0' diff --git a/controller/api/fixtures/tests.json b/controller/api/fixtures/tests.json index 7cbfc05bd6..16e1f0fddc 100644 --- a/controller/api/fixtures/tests.json +++ b/controller/api/fixtures/tests.json @@ -34,5 +34,23 @@ "email": "autotest2@deis.io", "date_joined": "2013-05-10T16:08:09.357Z" } +}, +{ + "pk": 9, + "model": "auth.user", + "fields": { + "username": "autotest3", + "first_name": "Otto", + "last_name": "Test", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2013-05-10T16:08:09.357Z", + "groups": [], + "user_permissions": [], + "password": "pbkdf2_sha256$10000$5Uoq7dl61vnN$gQhDpc2q2Rkn16VdPC+pNNEQcKpy+LGe29Zkad+2/m4=", + "email": "autotest3@deis.io", + "date_joined": "2013-05-10T16:08:09.357Z" + } } ] diff --git a/controller/api/tests/test_app.py b/controller/api/tests/test_app.py index dac8def3ed..72af156dde 100644 --- a/controller/api/tests/test_app.py +++ b/controller/api/tests/test_app.py @@ -324,6 +324,52 @@ def test_app_info_not_showing_wrong_app(self): response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) self.assertEqual(response.status_code, 404) + def test_app_transfer(self): + owner = User.objects.get(username='autotest2') + owner_token = Token.objects.get(user=owner).key + app_id = 'autotest' + base_url = '/v1/apps' + body = {'id': app_id} + response = self.client.post(base_url, json.dumps(body), content_type='application/json', + HTTP_AUTHORIZATION='token {}'.format(owner_token)) + # Transfer App + url = '{}/{}'.format(base_url, app_id) + new_owner = User.objects.get(username='autotest3') + new_owner_token = Token.objects.get(user=new_owner).key + body = {'owner': new_owner.username} + response = self.client.post(url, json.dumps(body), content_type='application/json', + HTTP_AUTHORIZATION='token {}'.format(owner_token)) + self.assertEqual(response.status_code, 200) + + # Original user can no longer access it + response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(owner_token)) + self.assertEqual(response.status_code, 403) + + # New owner can access it + response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(new_owner_token)) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data['owner'], new_owner.username) + + # Collaborators can't transfer + body = {'username': owner.username} + perms_url = url+"/perms/" + response = self.client.post(perms_url, json.dumps(body), content_type='application/json', + HTTP_AUTHORIZATION='token {}'.format(new_owner_token)) + self.assertEqual(response.status_code, 201) + body = {'owner': self.user.username} + response = self.client.post(url, json.dumps(body), content_type='application/json', + HTTP_AUTHORIZATION='token {}'.format(owner_token)) + self.assertEqual(response.status_code, 403) + + # Admins can transfer + body = {'owner': self.user.username} + response = self.client.post(url, json.dumps(body), content_type='application/json', + HTTP_AUTHORIZATION='token {}'.format(self.token)) + self.assertEqual(response.status_code, 200) + response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data['owner'], self.user.username) + FAKE_LOG_DATA = """ 2013-08-15 12:41:25 [33454] [INFO] Starting gunicorn 17.5 diff --git a/controller/api/tests/test_users.py b/controller/api/tests/test_users.py index 0dcaee83d1..55c3963689 100644 --- a/controller/api/tests/test_users.py +++ b/controller/api/tests/test_users.py @@ -21,9 +21,7 @@ def test_super_user_can_list(self): HTTP_AUTHORIZATION='token {}'.format(token)) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.data['results']), 2) - self.assertEqual(response.data['results'][0]['username'], 'autotest') - self.assertEqual(response.data['results'][1]['username'], 'autotest2') + self.assertEqual(len(response.data['results']), 3) def test_non_super_user_cannot_list(self): url = '/v1/users/' diff --git a/controller/api/urls.py b/controller/api/urls.py index 1c59c3fe73..99eada5fb9 100644 --- a/controller/api/urls.py +++ b/controller/api/urls.py @@ -63,7 +63,7 @@ views.AppPermsViewSet.as_view({'get': 'list', 'post': 'create'})), # apps base endpoint url(r"^apps/(?P{})/?".format(settings.APP_URL_REGEX), - views.AppViewSet.as_view({'get': 'retrieve', 'delete': 'destroy'})), + views.AppViewSet.as_view({'get': 'retrieve', 'post': 'update', 'delete': 'destroy'})), url(r'^apps/?', views.AppViewSet.as_view({'get': 'list', 'post': 'create'})), # key diff --git a/controller/api/views.py b/controller/api/views.py index 99ad471e7f..57bf3c7431 100644 --- a/controller/api/views.py +++ b/controller/api/views.py @@ -224,6 +224,17 @@ def run(self, request, **kwargs): return Response(output_and_rc, status=status.HTTP_200_OK, content_type='text/plain') + def update(self, request, **kwargs): + app = self.get_object() + + if request.data.get('owner'): + if self.request.user != app.owner and not self.request.user.is_superuser: + raise PermissionDenied() + new_owner = get_object_or_404(User, username=request.data['owner']) + app.owner = new_owner + app.save() + return Response(status=status.HTTP_200_OK) + class BuildViewSet(ReleasableViewSet): """A viewset for interacting with Build objects.""" diff --git a/docs/reference/api-v1.6.rst b/docs/reference/api-v1.6.rst index 1ad7a4bc8e..08a78b9aec 100644 --- a/docs/reference/api-v1.6.rst +++ b/docs/reference/api-v1.6.rst @@ -1,8 +1,6 @@ :title: Controller API v1.6 :description: The v1.6 REST API for Deis' Controller -.. _controller_api_v1: - Controller API v1.6 =================== @@ -18,6 +16,7 @@ What's New **New!** ``?page_size`` query parameter for paginated requests to set the number of results per page. + Authentication -------------- diff --git a/docs/reference/api-v1.7.rst b/docs/reference/api-v1.7.rst new file mode 100644 index 0000000000..6745252aaf --- /dev/null +++ b/docs/reference/api-v1.7.rst @@ -0,0 +1,1482 @@ +:title: Controller API v1.7 +:description: The v1.7 REST API for Deis' Controller + +.. _controller_api_v1: + +Controller API v1.7 +=================== + +This is the v1.7 REST API for the :ref:`Controller`. + + +What's New +---------- + +**New!** apps can now be updated ``POST /v1/apps/``. + + +Authentication +-------------- + + +Register a New User +``````````````````` + +Example Request: + +.. code-block:: console + + POST /v1/auth/register/ HTTP/1.1 + Host: deis.example.com + Content-Type: application/json + + { + "username": "test", + "password": "opensesame", + "email": "test@example.com" + } + +Optional Parameters: + +.. code-block:: console + + { + "first_name": "test", + "last_name": "testerson" + } + +Example Response: + +.. code-block:: console + + HTTP/1.1 201 CREATED + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + { + "id": 1, + "last_login": "2014-10-19T22:01:00.601Z", + "is_superuser": true, + "username": "test", + "first_name": "test", + "last_name": "testerson", + "email": "test@example.com", + "is_staff": true, + "is_active": true, + "date_joined": "2014-10-19T22:01:00.601Z", + "groups": [], + "user_permissions": [] + } + + +Log in +`````` + +Example Request: + +.. code-block:: console + + POST /v1/auth/login/ HTTP/1.1 + Host: deis.example.com + Content-Type: application/json + + {"username": "test", "password": "opensesame"} + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + {"token": "abc123"} + + +Cancel Account +`````````````` + +Example Request: + +.. code-block:: console + + DELETE /v1/auth/cancel/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 204 NO CONTENT + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + +Regenerate Token +```````````````` + +.. note:: + + This command could require administrative privileges + +Example Request: + +.. code-block:: console + + POST /v1/auth/tokens/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Optional Parameters: + +.. code-block:: console + + { + "username" : "test" + "all" : "true" + } + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + {"token": "abc123"} + +Change Password +``````````````` + +Example Request: + +.. code-block:: console + + POST /v1/auth/passwd/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + + { + "password": "foo", + "new_password": "bar" + } + +Optional parameters: + +.. code-block:: console + + {"username": "testuser"} + +.. note:: + + Using the ``username`` parameter requires administrative privileges and + makes the ``password`` parameter optional. + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + + +Applications +------------ + + +List all Applications +````````````````````` + +Example Request: + +.. code-block:: console + + GET /v1/apps HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "created": "2014-01-01T00:00:00UTC", + "id": "example-go", + "owner": "test", + "structure": {}, + "updated": "2014-01-01T00:00:00UTC", + "url": "example-go.example.com", + "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75" + } + ] + } + + +Create an Application +````````````````````` + +Example Request: + +.. code-block:: console + + POST /v1/apps/ HTTP/1.1 + Host: deis.example.com + Content-Type: application/json + Authorization: token abc123 + +Optional parameters: + +.. code-block:: console + + {"id": "example-go"} + + +Example Response: + +.. code-block:: console + + HTTP/1.1 201 CREATED + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + { + "created": "2014-01-01T00:00:00UTC", + "id": "example-go", + "owner": "test", + "structure": {}, + "updated": "2014-01-01T00:00:00UTC", + "url": "example-go.example.com", + "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75" + } + + +Destroy an Application +`````````````````````` + +Example Request: + +.. code-block:: console + + DELETE /v1/apps/example-go/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 204 NO CONTENT + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + + +List Application Details +```````````````````````` + +Example Request: + +.. code-block:: console + + GET /v1/apps/example-go/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + { + "created": "2014-01-01T00:00:00UTC", + "id": "example-go", + "owner": "test", + "structure": {}, + "updated": "2014-01-01T00:00:00UTC", + "url": "example-go.example.com", + "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75" + } + +Update Application Details +`````````````````````````` + +Example Request: + +.. code-block:: console + + POST /v1/apps/example-go/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Optional parameters: + +.. code-block:: console + + { + "owner": "test" + } + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.8.0 + Content-Type: application/json + +Retrieve Application Logs +````````````````````````` + +Example Request: + +.. code-block:: console + + GET /v1/apps/example-go/logs/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Optional URL Query Parameters: + +.. code-block:: console + + ?log_lines= + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: text/plain + + "16:51:14 deis[api]: test created initial release\n" + + +Run one-off Commands +```````````````````` + +.. code-block:: console + + POST /v1/apps/example-go/run/ HTTP/1.1 + Host: deis.example.com + Content-Type: application/json + Authorization: token abc123 + + {"command": "echo hi"} + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + [0, "hi\n"] + + +Certificates +------------ + + +List all Certificates +````````````````````` + +Example Request: + +.. code-block:: console + + GET /v1/certs HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "common_name": "test.example.com", + "expires": "2014-01-01T00:00:00UTC" + } + ] + } + + +List Certificate Details +```````````````````````` + +Example Request: + +.. code-block:: console + + GET /v1/certs/test.example.com HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + { + "updated": "2014-01-01T00:00:00UTC", + "created": "2014-01-01T00:00:00UTC", + "expires": "2015-01-01T00:00:00UTC", + "common_name": "test.example.com", + "owner": "test", + "id": 1 + } + + +Create Certificate +`````````````````` + +Example Request: + +.. code-block:: console + + POST /v1/certs/ HTTP/1.1 + Host: deis.example.com + Content-Type: application/json + Authorization: token abc123 + + { + "certificate": "-----BEGIN CERTIFICATE-----", + "key": "-----BEGIN RSA PRIVATE KEY-----" + } + +Optional Parameters: + +.. code-block:: console + + { + "common_name": "test.example.com" + } + + +Example Response: + +.. code-block:: console + + HTTP/1.1 201 CREATED + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + { + "updated": "2014-01-01T00:00:00UTC", + "created": "2014-01-01T00:00:00UTC", + "expires": "2015-01-01T00:00:00UTC", + "common_name": "test.example.com", + "owner": "test", + "id": 1 + } + + +Destroy a Certificate +````````````````````` + +Example Request: + +.. code-block:: console + + DELETE /v1/certs/test.example.com HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 204 NO CONTENT + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + + +Containers +---------- + + +List all Containers +``````````````````` + +Example Request: + +.. code-block:: console + + GET /v1/apps/example-go/containers/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "owner": "test", + "app": "example-go", + "release": "v2", + "created": "2014-01-01T00:00:00UTC", + "updated": "2014-01-01T00:00:00UTC", + "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75", + "type": "web", + "num": 1, + "state": "up" + } + ] + } + + +List all Containers by Type +``````````````````````````` + +Example Request: + +.. code-block:: console + + GET /v1/apps/example-go/containers/web/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "owner": "test", + "app": "example-go", + "release": "v2", + "created": "2014-01-01T00:00:00UTC", + "updated": "2014-01-01T00:00:00UTC", + "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75", + "type": "web", + "num": 1, + "state": "up" + } + ] + } + + +Restart All Containers +`````````````````````` + +Example Request: + +.. code-block:: console + + POST /v1/apps/example-go/containers/restart/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + [ + { + "owner": "test", + "app": "example-go", + "release": "v2", + "created": "2014-01-01T00:00:00UTC", + "updated": "2014-01-01T00:00:00UTC", + "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75", + "type": "web", + "num": 1, + "state": "up" + } + ] + + +Restart Containers by Type +`````````````````````````` + +Example Request: + +.. code-block:: console + + POST /v1/apps/example-go/containers/web/restart/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + [ + { + "owner": "test", + "app": "example-go", + "release": "v2", + "created": "2014-01-01T00:00:00UTC", + "updated": "2014-01-01T00:00:00UTC", + "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75", + "type": "web", + "num": 1, + "state": "up" + } + ] + + +Restart Containers by Type and Number +````````````````````````````````````` + +Example Request: + +.. code-block:: console + + POST /v1/apps/example-go/containers/web/1/restart/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + [ + { + "owner": "test", + "app": "example-go", + "release": "v2", + "created": "2014-01-01T00:00:00UTC", + "updated": "2014-01-01T00:00:00UTC", + "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75", + "type": "web", + "num": 1, + "state": "up" + } + ] + + +Scale Containers +```````````````` + +Example Request: + +.. code-block:: console + + POST /v1/apps/example-go/scale/ HTTP/1.1 + Host: deis.example.com + Content-Type: application/json + Authorization: token abc123 + + {"web": 3} + +Example Response: + +.. code-block:: console + + HTTP/1.1 204 NO CONTENT + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + + +Configuration +------------- + + +List Application Configuration +`````````````````````````````` + +Example Request: + +.. code-block:: console + + GET /v1/apps/example-go/config/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + { + "owner": "test", + "app": "example-go", + "values": { + "PLATFORM": "deis" + }, + "memory": {}, + "cpu": {}, + "tags": {}, + "created": "2014-01-01T00:00:00UTC", + "updated": "2014-01-01T00:00:00UTC", + "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75" + } + + +Create new Config +````````````````` + +Example Request: + +.. code-block:: console + + POST /v1/apps/example-go/config/ HTTP/1.1 + Host: deis.example.com + Content-Type: application/json + Authorization: token abc123 + + {"values": {"HELLO": "world", "PLATFORM": "deis"}} + +Example Response: + +.. code-block:: console + + HTTP/1.1 201 CREATED + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + X-Deis-Release: 3 + + { + "owner": "test", + "app": "example-go", + "values": { + "DEIS_APP": "example-go", + "DEIS_RELEASE": "v3", + "HELLO": "world", + "PLATFORM": "deis" + + }, + "memory": {}, + "cpu": {}, + "tags": {}, + "created": "2014-01-01T00:00:00UTC", + "updated": "2014-01-01T00:00:00UTC", + "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75" + } + + +Unset Config Variable +````````````````````` + +Example Request: + +.. code-block:: console + + POST /v1/apps/example-go/config/ HTTP/1.1 + Host: deis.example.com + Content-Type: application/json + Authorization: token abc123 + + {"values": {"HELLO": null}} + +Example Response: + +.. code-block:: console + + HTTP/1.1 201 CREATED + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + X-Deis-Release: 4 + + { + "owner": "test", + "app": "example-go", + "values": { + "DEIS_APP": "example-go", + "DEIS_RELEASE": "v4", + "PLATFORM": "deis" + }, + "memory": {}, + "cpu": {}, + "tags": {}, + "created": "2014-01-01T00:00:00UTC", + "updated": "2014-01-01T00:00:00UTC", + "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75" + } + + +Domains +------- + + +List Application Domains +```````````````````````` + +Example Request: + +.. code-block:: console + + GET /v1/apps/example-go/domains/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "app": "example-go", + "created": "2014-01-01T00:00:00UTC", + "domain": "example.example.com", + "owner": "test", + "updated": "2014-01-01T00:00:00UTC" + } + ] + } + + +Add Domain +`````````` + +Example Request: + +.. code-block:: console + + POST /v1/apps/example-go/domains/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + + {'domain': 'example.example.com'} + +Example Response: + +.. code-block:: console + + HTTP/1.1 201 CREATED + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + { + "app": "example-go", + "created": "2014-01-01T00:00:00UTC", + "domain": "example.example.com", + "owner": "test", + "updated": "2014-01-01T00:00:00UTC" + } + + + +Remove Domain +````````````` + +Example Request: + +.. code-block:: console + + DELETE /v1/apps/example-go/domains/example.example.com HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 204 NO CONTENT + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + + +Builds +------ + + +List Application Builds +``````````````````````` + +Example Request: + +.. code-block:: console + + GET /v1/apps/example-go/builds/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "app": "example-go", + "created": "2014-01-01T00:00:00UTC", + "dockerfile": "FROM deis/slugrunner RUN mkdir -p /app WORKDIR /app ENTRYPOINT [\"/runner/init\"] ADD slug.tgz /app ENV GIT_SHA 060da68f654e75fac06dbedd1995d5f8ad9084db", + "image": "example-go", + "owner": "test", + "procfile": { + "web": "example-go" + }, + "sha": "060da68f", + "updated": "2014-01-01T00:00:00UTC", + "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75" + } + ] + } + + +Create Application Build +```````````````````````` + +Example Request: + +.. code-block:: console + + POST /v1/apps/example-go/builds/ HTTP/1.1 + Host: deis.example.com + Content-Type: application/json + Authorization: token abc123 + + {"image": "deis/example-go:latest"} + +Optional Parameters: + +.. code-block:: console + + { + "procfile": { + "web": "./cmd" + } + } + +Example Response: + +.. code-block:: console + + HTTP/1.1 201 CREATED + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + X-Deis-Release: 4 + + { + "app": "example-go", + "created": "2014-01-01T00:00:00UTC", + "dockerfile": "", + "image": "deis/example-go:latest", + "owner": "test", + "procfile": {}, + "sha": "", + "updated": "2014-01-01T00:00:00UTC", + "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75" + } + + +Releases +-------- + + +List Application Releases +````````````````````````` + +Example Request: + +.. code-block:: console + + GET /v1/apps/example-go/releases/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + { + "count": 3, + "next": null, + "previous": null, + "results": [ + { + "app": "example-go", + "build": "202d8e4b-600e-4425-a85c-ffc7ea607f61", + "config": "ed637ceb-5d32-44bd-9406-d326a777a513", + "created": "2014-01-01T00:00:00UTC", + "owner": "test", + "summary": "test changed nothing", + "updated": "2014-01-01T00:00:00UTC", + "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75", + "version": 3 + }, + { + "app": "example-go", + "build": "202d8e4b-600e-4425-a85c-ffc7ea607f61", + "config": "95bd6dea-1685-4f78-a03d-fd7270b058d1", + "created": "2014-01-01T00:00:00UTC", + "owner": "test", + "summary": "test deployed 060da68", + "updated": "2014-01-01T00:00:00UTC", + "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75", + "version": 2 + }, + { + "app": "example-go", + "build": null, + "config": "95bd6dea-1685-4f78-a03d-fd7270b058d1", + "created": "2014-01-01T00:00:00UTC", + "owner": "test", + "summary": "test created initial release", + "updated": "2014-01-01T00:00:00UTC", + "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75", + "version": 1 + } + ] + } + + +List Release Details +```````````````````` + +Example Request: + +.. code-block:: console + + GET /v1/apps/example-go/releases/v1/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + { + "app": "example-go", + "build": null, + "config": "95bd6dea-1685-4f78-a03d-fd7270b058d1", + "created": "2014-01-01T00:00:00UTC", + "owner": "test", + "summary": "test created initial release", + "updated": "2014-01-01T00:00:00UTC", + "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75", + "version": 1 + } + + +Rollback Release +```````````````` + +Example Request: + +.. code-block:: console + + POST /v1/apps/example-go/releases/rollback/ HTTP/1.1 + Host: deis.example.com + Content-Type: application/json + Authorization: token abc123 + + {"version": 1} + +Example Response: + +.. code-block:: console + + HTTP/1.1 201 CREATED + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + {"version": 5} + + +Keys +---- + + +List Keys +````````` + +Example Request: + +.. code-block:: console + + GET /v1/keys/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 201 CREATED + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "created": "2014-01-01T00:00:00UTC", + "id": "test@example.com", + "owner": "test", + "public": "ssh-rsa <...>", + "updated": "2014-01-01T00:00:00UTC", + "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75" + } + ] + } + + +Add Key to User +``````````````` + +Example Request: + +.. code-block:: console + + POST /v1/keys/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + + { + "id": "example", + "public": "ssh-rsa <...>" + } + +Example Response: + +.. code-block:: console + + HTTP/1.1 201 CREATED + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + { + "created": "2014-01-01T00:00:00UTC", + "id": "example", + "owner": "example", + "public": "ssh-rsa <...>", + "updated": "2014-01-01T00:00:00UTC", + "uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75" + } + + +Remove Key from User +```````````````````` + +Example Request: + +.. code-block:: console + + DELETE /v1/keys/example HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 204 NO CONTENT + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + + +Permissions +----------- + + +List Application Permissions +```````````````````````````` + +.. note:: + + This does not include the app owner. + +Example Request: + +.. code-block:: console + + GET /v1/apps/example-go/perms/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + { + "users": [ + "test", + "foo" + ] + } + + +Create Application Permission +````````````````````````````` + +Example Request: + +.. code-block:: console + + POST /v1/apps/example-go/perms/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + + {"username": "example"} + +Example Response: + +.. code-block:: console + + HTTP/1.1 201 CREATED + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + + +Remove Application Permission +````````````````````````````` + +Example Request: + +.. code-block:: console + + DELETE /v1/apps/example-go/perms/example HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 204 NO CONTENT + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + +List Administrators +``````````````````` + +Example Request: + +.. code-block:: console + + GET /v1/admin/perms/ HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + { + "count": 2, + "next": null + "previous": null, + "results": [ + { + "username": "test", + "is_superuser": true + }, + { + "username": "foo", + "is_superuser": true + } + ] + } + + +Grant User Administrative Privileges +```````````````````````````````````` + +.. note:: + + This command requires administrative privileges + +Example Request: + +.. code-block:: console + + POST /v1/admin/perms HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + + {"username": "example"} + +Example Response: + +.. code-block:: console + + HTTP/1.1 201 CREATED + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + +Remove User's Administrative Privileges +``````````````````````````````````````` + +.. note:: + + This command requires administrative privileges + +Example Request: + +.. code-block:: console + + DELETE /v1/admin/perms/example HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 204 NO CONTENT + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + +Users +----- + +List all users +`````````````` + +.. note:: + + This command requires administrative privileges + +Example Request: + +.. code-block:: console + + GET /v1/users HTTP/1.1 + Host: deis.example.com + Authorization: token abc123 + +Example Response: + +.. code-block:: console + + HTTP/1.1 200 OK + DEIS_API_VERSION: 1.7 + DEIS_PLATFORM_VERSION: 1.10.0 + Content-Type: application/json + + { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "id": 1, + "last_login": "2014-10-19T22:01:00.601Z", + "is_superuser": true, + "username": "test", + "first_name": "test", + "last_name": "testerson", + "email": "test@example.com", + "is_staff": true, + "is_active": true, + "date_joined": "2014-10-19T22:01:00.601Z", + "groups": [], + "user_permissions": [] + } + ] + } diff --git a/docs/reference/index.rst b/docs/reference/index.rst index c818a86e0b..afebace173 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -20,3 +20,4 @@ Reference Guide api-v1.4 api-v1.5 api-v1.6 + api-v1.7 diff --git a/tests/apps_test.go b/tests/apps_test.go index 84659c74bf..a8318b5b3e 100644 --- a/tests/apps_test.go +++ b/tests/apps_test.go @@ -23,6 +23,7 @@ var ( appsInfoCmd = "apps:info --app={{.AppName}}" appsDestroyCmd = "apps:destroy --app={{.AppName}} --confirm={{.AppName}}" appsDestroyCmdNoApp = "apps:destroy --confirm={{.AppName}}" + appsTransferCmd = "apps:transfer {{.NewOwner}} --app={{.AppName}}" ) func randomString(n int) string { @@ -44,6 +45,7 @@ func TestApps(t *testing.T) { appsOpenTest(t, params) appsDestroyTest(t, params) appsListTest(t, params, true) + appsTransferTest(t, params) } func appsSetup(t *testing.T) *utils.DeisTestConfig { @@ -127,3 +129,18 @@ func appsRunTest(t *testing.T, params *utils.DeisTestConfig) { } utils.Execute(t, cmd, params, true, "Not found") } + +func appsTransferTest(t *testing.T, params *utils.DeisTestConfig) { + user := utils.GetGlobalConfig() + user.UserName, user.Password = "app-transfer-test", "test" + user.AppName = "transfer-test" + user.NewOwner = params.UserName + utils.Execute(t, authRegisterCmd, user, false, "") + utils.Execute(t, authLoginCmd, user, false, "") + utils.Execute(t, appsCreateCmdNoRemote, user, false, "") + utils.Execute(t, appsTransferCmd, user, false, "") + utils.Execute(t, appsInfoCmd, user, true, "403 FORBIDDEN") + utils.Execute(t, authLoginCmd, params, false, "") + params.AppName = user.AppName + utils.CheckList(t, appsInfoCmd, params, params.UserName, false) +} diff --git a/tests/utils/itutils.go b/tests/utils/itutils.go index e60077bfe5..125bc447bf 100644 --- a/tests/utils/itutils.go +++ b/tests/utils/itutils.go @@ -37,6 +37,7 @@ type DeisTestConfig struct { UserName string Password string NewPassword string + NewOwner string Email string ExampleApp string AppDomain string diff --git a/version/version.go b/version/version.go index 73375f40e8..53dd48e3bb 100644 --- a/version/version.go +++ b/version/version.go @@ -4,4 +4,4 @@ package version const Version = "1.11.0-dev" // API identifies the latest Deis api verison -const APIVersion = "1.6" +const APIVersion = "1.7"