api: add support for deleting apps (#327)

* api: add support for deleting apps

Fixes #274

* functions: improve error name and description

* functions: fix test regression
This commit is contained in:
C Cirello
2016-11-22 01:07:30 +01:00
committed by GitHub
parent be6685b361
commit da96ef471a
5 changed files with 89 additions and 30 deletions

View File

@@ -8,16 +8,17 @@ import (
type Apps []*App type Apps []*App
var ( var (
ErrAppsCreate = errors.New("Could not create app") ErrAppsAlreadyExists = errors.New("App already exists")
ErrAppsUpdate = errors.New("Could not update app") ErrAppsCreate = errors.New("Could not create app")
ErrAppsRemoving = errors.New("Could not remove app from datastore") ErrAppsGet = errors.New("Could not get app from datastore")
ErrAppsGet = errors.New("Could not get app from datastore") ErrAppsList = errors.New("Could not list apps from datastore")
ErrAppsList = errors.New("Could not list apps from datastore") ErrAppsMissingNew = errors.New("Missing new application")
ErrAppsAlreadyExists = errors.New("App already exists") ErrAppsNotFound = errors.New("App not found")
ErrAppsNotFound = errors.New("App not found") ErrAppsNothingToUpdate = errors.New("Nothing to update")
ErrAppsNothingToUpdate = errors.New("Nothing to update") ErrAppsRemoving = errors.New("Could not remove app from datastore")
ErrAppsMissingNew = errors.New("Missing new application") ErrDeleteAppsWithRoutes = errors.New("Cannot remove apps with routes")
ErrUsableImage = errors.New("Image not found") ErrAppsUpdate = errors.New("Could not update app")
ErrUsableImage = errors.New("Image not found")
) )
type App struct { type App struct {

View File

@@ -14,13 +14,25 @@ func handleAppDelete(c *gin.Context) {
log := common.Logger(ctx) log := common.Logger(ctx)
appName := c.Param("app") appName := c.Param("app")
err := Api.Datastore.RemoveApp(appName)
routes, err := Api.Datastore.GetRoutesByApp(appName, &models.RouteFilter{})
if err != nil { if err != nil {
log.WithError(err).Debug(models.ErrAppsRemoving) log.WithError(err).Debug(models.ErrAppsRemoving)
c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsRemoving)) c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsRemoving))
return return
} }
if len(routes) > 0 {
log.WithError(err).Debug(models.ErrDeleteAppsWithRoutes)
c.JSON(http.StatusBadRequest, simpleError(models.ErrDeleteAppsWithRoutes))
return
}
if err := Api.Datastore.RemoveApp(appName); err != nil {
log.WithError(err).Debug(models.ErrAppsRemoving)
c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsRemoving))
return
}
c.JSON(http.StatusOK, gin.H{"message": "App deleted"}) c.JSON(http.StatusOK, gin.H{"message": "App deleted"})
} }

View File

@@ -97,33 +97,36 @@ func TestFullStack(t *testing.T) {
router := testRouter(ds, &mqs.Mock{}, testRunner(t), tasks) router := testRouter(ds, &mqs.Mock{}, testRunner(t), tasks)
for i, test := range []struct { for _, test := range []struct {
name string
method string method string
path string path string
body string body string
expectedCode int expectedCode int
}{ }{
{"POST", "/v1/apps", `{ "app": { "name": "myapp" } }`, http.StatusCreated}, {"create my app", "POST", "/v1/apps", `{ "app": { "name": "myapp" } }`, http.StatusCreated},
{"GET", "/v1/apps", ``, http.StatusOK}, {"list apps", "GET", "/v1/apps", ``, http.StatusOK},
{"GET", "/v1/apps/myapp", ``, http.StatusOK}, {"get app", "GET", "/v1/apps/myapp", ``, http.StatusOK},
{"POST", "/v1/apps/myapp/routes", `{ "route": { "name": "myroute", "path": "/myroute", "image": "iron/hello" } }`, http.StatusCreated}, {"add myroute", "POST", "/v1/apps/myapp/routes", `{ "route": { "name": "myroute", "path": "/myroute", "image": "iron/hello" } }`, http.StatusCreated},
{"POST", "/v1/apps/myapp/routes", `{ "route": { "name": "myroute2", "path": "/myroute2", "image": "iron/error" } }`, http.StatusCreated}, {"add myroute2", "POST", "/v1/apps/myapp/routes", `{ "route": { "name": "myroute2", "path": "/myroute2", "image": "iron/error" } }`, http.StatusCreated},
{"GET", "/v1/apps/myapp/routes/myroute", ``, http.StatusOK}, {"get myroute", "GET", "/v1/apps/myapp/routes/myroute", ``, http.StatusOK},
{"GET", "/v1/apps/myapp/routes/myroute2", ``, http.StatusOK}, {"get myroute2", "GET", "/v1/apps/myapp/routes/myroute2", ``, http.StatusOK},
{"GET", "/v1/apps/myapp/routes", ``, http.StatusOK}, {"get all routes", "GET", "/v1/apps/myapp/routes", ``, http.StatusOK},
{"POST", "/r/myapp/myroute", `{ "name": "Teste" }`, http.StatusOK}, {"execute myroute", "POST", "/r/myapp/myroute", `{ "name": "Teste" }`, http.StatusOK},
{"POST", "/r/myapp/myroute2", `{ "name": "Teste" }`, http.StatusInternalServerError}, {"execute myroute2", "POST", "/r/myapp/myroute2", `{ "name": "Teste" }`, http.StatusInternalServerError},
{"DELETE", "/v1/apps/myapp/routes/myroute", ``, http.StatusOK}, {"delete myroute", "DELETE", "/v1/apps/myapp/routes/myroute", ``, http.StatusOK},
{"DELETE", "/v1/apps/myapp", ``, http.StatusOK}, {"delete app (fail)", "DELETE", "/v1/apps/myapp", ``, http.StatusBadRequest},
{"GET", "/v1/apps/myapp", ``, http.StatusNotFound}, {"delete myroute2", "DELETE", "/v1/apps/myapp/routes/myroute2", ``, http.StatusOK},
{"GET", "/v1/apps/myapp/routes/myroute", ``, http.StatusInternalServerError}, {"delete app (success)", "DELETE", "/v1/apps/myapp", ``, http.StatusOK},
{"get deleted app", "GET", "/v1/apps/myapp", ``, http.StatusNotFound},
{"get delete route on deleted app", "GET", "/v1/apps/myapp/routes/myroute", ``, http.StatusInternalServerError},
} { } {
_, rec := routerRequest(t, router, test.method, test.path, bytes.NewBuffer([]byte(test.body))) _, rec := routerRequest(t, router, test.method, test.path, bytes.NewBuffer([]byte(test.body)))
if rec.Code != test.expectedCode { if rec.Code != test.expectedCode {
t.Log(buf.String()) t.Log(buf.String())
t.Errorf("Test %d: Expected status code to be %d but was %d", t.Errorf("Test \"%s\": Expected status code to be %d but was %d",
i, test.expectedCode, rec.Code) test.name, test.expectedCode, rec.Code)
} }
} }
} }

View File

@@ -6,7 +6,7 @@ swagger: '2.0'
info: info:
title: IronFunctions title: IronFunctions
description: The open source serverless platform. description: The open source serverless platform.
version: "0.1.16" version: "0.1.17"
# the domain of the service # the domain of the service
host: "127.0.0.1:8080" host: "127.0.0.1:8080"
# array of all schemes that your API supports # array of all schemes that your API supports
@@ -67,6 +67,20 @@ paths:
/apps/{app}: /apps/{app}:
delete:
summary: "Delete an app."
description: "Delete an app."
tags:
- Apps
parameters:
- name: app
in: path
description: Name of the app.
required: true
type: string
responses:
200:
description: Apps successfully deleted.
get: get:
summary: "Get information for a app." summary: "Get information for a app."
description: "This gives more details about a app, such as statistics." description: "This gives more details about a app, such as statistics."
@@ -271,7 +285,7 @@ paths:
type: string type: string
responses: responses:
200: 200:
description: Route successfully deleted. Deletion succeeds even on routes that do not exist. description: Route successfully deleted.
/tasks: /tasks:
get: get:

View File

@@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/http"
"os" "os"
"text/tabwriter" "text/tabwriter"
@@ -79,6 +80,11 @@ func apps() cli.Command {
}, },
}, },
}, },
{
Name: "delete",
Usage: "delete an app",
Action: a.delete,
},
}, },
} }
} }
@@ -249,3 +255,26 @@ func (a *appsCmd) storeApp(appName string, config map[string]string) error {
} }
return nil return nil
} }
func (a *appsCmd) delete(c *cli.Context) error {
appName := c.Args().First()
if appName == "" {
return errors.New("error: deleting an app takes one argument, an app name")
}
if err := resetBasePath(a.Configuration); err != nil {
return fmt.Errorf("error setting endpoint: %v", err)
}
resp, err := a.AppsAppDelete(appName)
if err != nil {
return fmt.Errorf("error deleting app: %v", err)
}
if resp.StatusCode == http.StatusBadRequest {
return errors.New("could not delete this application - pending routes")
}
fmt.Println(appName, "deleted")
return nil
}