Merge pull request #36 from coreos/gc
api/database: Add the ability to delete layers
This commit is contained in:
commit
15bc682f60
@ -51,6 +51,18 @@ func POSTLayers(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|||||||
jsonhttp.Render(w, http.StatusCreated, struct{ Version string }{Version: strconv.Itoa(worker.Version)})
|
jsonhttp.Render(w, http.StatusCreated, struct{ Version string }{Version: strconv.Itoa(worker.Version)})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteLayer deletes the specified layer and any child layers that are
|
||||||
|
// dependent on the specified layer.
|
||||||
|
func DELETELayers(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||||
|
err := database.DeleteLayer(p.ByName("id"))
|
||||||
|
if err != nil {
|
||||||
|
jsonhttp.RenderError(w, 0, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonhttp.Render(w, http.StatusNoContent, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// GETLayersOS returns the operating system of a layer if it exists.
|
// GETLayersOS returns the operating system of a layer if it exists.
|
||||||
// It uses not only the specified layer but also its parent layers if necessary.
|
// It uses not only the specified layer but also its parent layers if necessary.
|
||||||
// An empty OS string is returned if no OS has been detected.
|
// An empty OS string is returned if no OS has been detected.
|
||||||
|
@ -68,6 +68,7 @@ func NewRouterV1(to time.Duration) *httprouter.Router {
|
|||||||
|
|
||||||
// Layers
|
// Layers
|
||||||
router.POST("/layers", wrap(logic.POSTLayers))
|
router.POST("/layers", wrap(logic.POSTLayers))
|
||||||
|
router.DELETE("/layers/:id", wrap(logic.DELETELayers))
|
||||||
router.GET("/layers/:id/os", wrap(logic.GETLayersOS))
|
router.GET("/layers/:id/os", wrap(logic.GETLayersOS))
|
||||||
router.GET("/layers/:id/parent", wrap(logic.GETLayersParent))
|
router.GET("/layers/:id/parent", wrap(logic.GETLayersParent))
|
||||||
router.GET("/layers/:id/packages", wrap(logic.GETLayersPackages))
|
router.GET("/layers/:id/packages", wrap(logic.GETLayersPackages))
|
||||||
|
@ -131,6 +131,60 @@ func InsertLayer(layer *Layer) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteLayer deletes the specified layer and any child layers that are
|
||||||
|
// dependent on the specified layer.
|
||||||
|
func DeleteLayer(ID string) error {
|
||||||
|
layer, err := FindOneLayerByID(ID, []string{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return deleteLayerTreeFrom(layer.Node, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteLayerTreeFrom(node string, t *graph.Transaction) error {
|
||||||
|
// Determine if that function call is the root call of the recursivity
|
||||||
|
// And create transaction if its the case.
|
||||||
|
root := (t == nil)
|
||||||
|
if root {
|
||||||
|
t = cayley.NewTransaction()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find layer.
|
||||||
|
layer, err := FindOneLayerByNode(node, FieldLayerAll)
|
||||||
|
if err != nil {
|
||||||
|
// Ignore missing layer.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all successor layers.
|
||||||
|
for _, succNode := range layer.SuccessorsNodes {
|
||||||
|
deleteLayerTreeFrom(succNode, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove layer.
|
||||||
|
t.RemoveQuad(cayley.Quad(layer.Node, FieldIs, FieldLayerIsValue, ""))
|
||||||
|
t.RemoveQuad(cayley.Quad(layer.Node, FieldLayerID, layer.ID, ""))
|
||||||
|
t.RemoveQuad(cayley.Quad(layer.Node, FieldLayerParent, layer.ParentNode, ""))
|
||||||
|
t.RemoveQuad(cayley.Quad(layer.Node, FieldLayerOS, layer.OS, ""))
|
||||||
|
t.RemoveQuad(cayley.Quad(layer.Node, FieldLayerEngineVersion, strconv.Itoa(layer.EngineVersion), ""))
|
||||||
|
for _, pkg := range layer.InstalledPackagesNodes {
|
||||||
|
t.RemoveQuad(cayley.Quad(layer.Node, FieldLayerInstalledPackages, pkg, ""))
|
||||||
|
}
|
||||||
|
for _, pkg := range layer.RemovedPackagesNodes {
|
||||||
|
t.RemoveQuad(cayley.Quad(layer.Node, FieldLayerRemovedPackages, pkg, ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply transaction if root call.
|
||||||
|
if root {
|
||||||
|
if err = store.ApplyTransaction(t); err != nil {
|
||||||
|
log.Errorf("failed transaction (deleteLayerTreeFrom): %s", err)
|
||||||
|
return ErrTransaction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// FindOneLayerByID finds and returns a single layer having the given ID,
|
// FindOneLayerByID finds and returns a single layer having the given ID,
|
||||||
// selecting the specified fields and hardcoding its ID
|
// selecting the specified fields and hardcoding its ID
|
||||||
func FindOneLayerByID(ID string, selectedFields []string) (*Layer, error) {
|
func FindOneLayerByID(ID string, selectedFields []string) (*Layer, error) {
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coreos/clair/utils"
|
"github.com/coreos/clair/utils"
|
||||||
|
cerrors "github.com/coreos/clair/utils/errors"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -38,7 +39,7 @@ func TestLayerSimple(t *testing.T) {
|
|||||||
// Insert a layer and find it back
|
// Insert a layer and find it back
|
||||||
l1 := &Layer{ID: "l1", OS: "os1", InstalledPackagesNodes: []string{"p1", "p2"}, EngineVersion: 1}
|
l1 := &Layer{ID: "l1", OS: "os1", InstalledPackagesNodes: []string{"p1", "p2"}, EngineVersion: 1}
|
||||||
if assert.Nil(t, InsertLayer(l1)) {
|
if assert.Nil(t, InsertLayer(l1)) {
|
||||||
fl1, err := FindOneLayerByID("l1", FieldLayerAll)
|
fl1, err := FindOneLayerByID(l1.ID, FieldLayerAll)
|
||||||
if assert.Nil(t, err) && assert.NotNil(t, fl1) {
|
if assert.Nil(t, err) && assert.NotNil(t, fl1) {
|
||||||
// Saved = found
|
// Saved = found
|
||||||
assert.True(t, layerEqual(l1, fl1), "layers are not equal, expected %v, have %s", l1, fl1)
|
assert.True(t, layerEqual(l1, fl1), "layers are not equal, expected %v, have %s", l1, fl1)
|
||||||
@ -66,6 +67,12 @@ func TestLayerSimple(t *testing.T) {
|
|||||||
if assert.Nil(t, err) && assert.Len(t, al1, 1) {
|
if assert.Nil(t, err) && assert.Len(t, al1, 1) {
|
||||||
assert.Equal(t, al1[0].Node, l1.Node)
|
assert.Equal(t, al1[0].Node, l1.Node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
if assert.Nil(t, DeleteLayer(l1.ID)) {
|
||||||
|
_, err := FindOneLayerByID(l1.ID, FieldLayerAll)
|
||||||
|
assert.Equal(t, cerrors.ErrNotFound, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,6 +126,14 @@ func TestLayerTree(t *testing.T) {
|
|||||||
fl4bpkg, err := flayers[4].AllPackages()
|
fl4bpkg, err := flayers[4].AllPackages()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Len(t, fl4bpkg, 0)
|
assert.Len(t, fl4bpkg, 0)
|
||||||
|
|
||||||
|
// Delete a layer in the middle of the tree.
|
||||||
|
if assert.Nil(t, DeleteLayer(flayers[1].ID)) {
|
||||||
|
for _, l := range layers[1:] {
|
||||||
|
_, err := FindOneLayerByID(l.ID, FieldLayerAll)
|
||||||
|
assert.Equal(t, cerrors.ErrNotFound, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
36
docs/API.md
36
docs/API.md
@ -150,6 +150,41 @@ HTTP/1.1 400 Bad Request
|
|||||||
|
|
||||||
It could also return a `415 Unsupported Media Type` response with a `Message` if the request content is not valid JSON.
|
It could also return a `415 Unsupported Media Type` response with a `Message` if the request content is not valid JSON.
|
||||||
|
|
||||||
|
## Delete a Layer
|
||||||
|
|
||||||
|
It deletes a layer from the database and any child layers that are dependent on the specified layer.
|
||||||
|
|
||||||
|
DELETE /v1/layers/{ID}
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
|Name|Type|Description|
|
||||||
|
|------|-----|-------------|
|
||||||
|
|ID|String|Unique ID of the Layer|
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -s -X DELETE 127.0.0.1:6060/v1/layers/39bb80489af75406073b5364c9c326134015140e1f7976a370a8bd446889e6f8
|
||||||
|
```
|
||||||
|
|
||||||
|
### Success Response
|
||||||
|
|
||||||
|
```
|
||||||
|
HTTP/1.1 204 No Content
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Response
|
||||||
|
|
||||||
|
```
|
||||||
|
HTTP/1.1 404 Not Found
|
||||||
|
{
|
||||||
|
"Message": "the resource cannot be found"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
//////////
|
||||||
|
|
||||||
## Get a Layer's operating system
|
## Get a Layer's operating system
|
||||||
|
|
||||||
It returns the operating system a given Layer.
|
It returns the operating system a given Layer.
|
||||||
@ -210,6 +245,7 @@ HTTP/1.1 200 OK
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Error Response
|
### Error Response
|
||||||
|
|
||||||
```
|
```
|
||||||
HTTP/1.1 404 Not Found
|
HTTP/1.1 404 Not Found
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user