Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api/database: Add the ability to delete layers #36

Merged
merged 1 commit into from
Dec 4, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions api/logic/layers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)})
}

// 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.
// 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.
Expand Down
1 change: 1 addition & 0 deletions api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func NewRouterV1(to time.Duration) *httprouter.Router {

// Layers
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/parent", wrap(logic.GETLayersParent))
router.GET("/layers/:id/packages", wrap(logic.GETLayersPackages))
Expand Down
54 changes: 54 additions & 0 deletions database/layer.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,60 @@ func InsertLayer(layer *Layer) error {
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,
// selecting the specified fields and hardcoding its ID
func FindOneLayerByID(ID string, selectedFields []string) (*Layer, error) {
Expand Down
17 changes: 16 additions & 1 deletion database/layer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"testing"

"github.com/coreos/clair/utils"
cerrors "github.com/coreos/clair/utils/errors"
"github.com/stretchr/testify/assert"
)

Expand All @@ -38,7 +39,7 @@ func TestLayerSimple(t *testing.T) {
// Insert a layer and find it back
l1 := &Layer{ID: "l1", OS: "os1", InstalledPackagesNodes: []string{"p1", "p2"}, EngineVersion: 1}
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) {
// Saved = found
assert.True(t, layerEqual(l1, fl1), "layers are not equal, expected %v, have %s", l1, fl1)
Expand Down Expand Up @@ -66,6 +67,12 @@ func TestLayerSimple(t *testing.T) {
if assert.Nil(t, err) && assert.Len(t, al1, 1) {
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)
}
}
}

Expand Down Expand Up @@ -119,6 +126,14 @@ func TestLayerTree(t *testing.T) {
fl4bpkg, err := flayers[4].AllPackages()
assert.Nil(t, err)
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)
}
}
}
}

Expand Down
36 changes: 36 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

## 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

It returns the operating system a given Layer.
Expand Down Expand Up @@ -210,6 +245,7 @@ HTTP/1.1 200 OK
```

### Error Response

```
HTTP/1.1 404 Not Found
{
Expand Down