diff --git a/server/datastore/datastore_labels_test.go b/server/datastore/datastore_labels_test.go index 575a965a0f..c89ef9fe4d 100644 --- a/server/datastore/datastore_labels_test.go +++ b/server/datastore/datastore_labels_test.go @@ -386,3 +386,42 @@ func testSaveLabel(t *testing.T, db kolide.Datastore) { assert.Equal(t, label.Name, saved.Name) assert.Equal(t, label.Description, saved.Description) } + +func testReplaceDeletedLabel(t *testing.T, db kolide.Datastore) { + if db.Name() == "inmem" { + t.Skip("inmem is being deprecated, test skipped") + } + + label := &kolide.Label{ + Name: "my label", + Query: "select 1 from processes;", + } + saved, err := db.NewLabel(label) + require.Nil(t, err) + + saved, err = db.Label(saved.ID) + require.Nil(t, err) + assert.Equal(t, label.Name, saved.Name) + assert.Equal(t, label.Description, saved.Description) + + newLabel := &kolide.Label{ + Name: "my label", + Query: " select * from time", + } + + // Replace should fail when label already exists and isn't soft deleted + _, err = db.NewLabel(newLabel) + require.NotNil(t, err) + + // Now delete label and replace should succeed + err = db.DeleteLabel(label.ID) + require.Nil(t, err) + + saved, err = db.NewLabel(newLabel) + require.Nil(t, err) + + saved, err = db.Label(saved.ID) + require.Nil(t, err) + assert.Equal(t, newLabel.Name, saved.Name) + assert.Equal(t, newLabel.Description, saved.Description) +} diff --git a/server/datastore/datastore_test.go b/server/datastore/datastore_test.go index 5f871f7b77..6fff7ee45e 100644 --- a/server/datastore/datastore_test.go +++ b/server/datastore/datastore_test.go @@ -73,4 +73,5 @@ var testFunctions = [...]func(*testing.T, kolide.Datastore){ testLicense, testSaveLabel, testFlappingNetworkInterfaces, + testReplaceDeletedLabel, } diff --git a/server/datastore/mysql/labels.go b/server/datastore/mysql/labels.go index 6e3373eeaa..d78ed029c2 100644 --- a/server/datastore/mysql/labels.go +++ b/server/datastore/mysql/labels.go @@ -11,8 +11,25 @@ import ( // NewLabel creates a new kolide.Label func (d *Datastore) NewLabel(label *kolide.Label) (*kolide.Label, error) { - - sql := ` + var ( + deletedLabel kolide.Label + query string + ) + err := d.db.Get(&deletedLabel, + "SELECT * FROM labels WHERE name = ? AND deleted", label.Name) + switch err { + case nil: + query = ` + REPLACE INTO labels ( + name, + description, + query, + platform, + label_type + ) VALUES ( ?, ?, ?, ?, ?) + ` + case sql.ErrNoRows: + query = ` INSERT INTO labels ( name, description, @@ -21,7 +38,10 @@ func (d *Datastore) NewLabel(label *kolide.Label) (*kolide.Label, error) { label_type ) VALUES ( ?, ?, ?, ?, ?) ` - result, err := d.db.Exec(sql, label.Name, label.Description, label.Query, label.Platform, label.LabelType) + default: + return nil, errors.Wrap(err, "check for existing label") + } + result, err := d.db.Exec(query, label.Name, label.Description, label.Query, label.Platform, label.LabelType) if err != nil { return nil, errors.Wrap(err, "inserting label") } diff --git a/server/service/endpoint_labels.go b/server/service/endpoint_labels.go index 43ccb17b85..a52d071502 100644 --- a/server/service/endpoint_labels.go +++ b/server/service/endpoint_labels.go @@ -119,11 +119,17 @@ func (r createLabelResponse) error() error { return r.Err } func makeCreateLabelEndpoint(svc kolide.Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(createLabelRequest) + label, err := svc.NewLabel(ctx, req.payload) + if err != nil { + return createLabelResponse{Err: err}, nil + } + labelResp, err := labelResponseForLabel(ctx, svc, label) if err != nil { return createLabelResponse{Err: err}, nil } + return createLabelResponse{Label: *labelResp}, nil } }