diff --git a/server/handlers/default.go b/server/handlers/default.go index cf8abf11fd..01e658945d 100644 --- a/server/handlers/default.go +++ b/server/handlers/default.go @@ -33,7 +33,7 @@ func MainHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *e return nil } -// AddHandler accepts urls in the form // +// AddHandler adds the provided json data for the role and GUN specified in the URL func UpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError { defer r.Body.Close() s := ctx.Value("versionStore") @@ -84,7 +84,7 @@ func UpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) return nil } -// GetHandler accepts urls in the form //.json +// GetHandler returns the json for a specified role and GUN. func GetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError { s := ctx.Value("versionStore") store, ok := s.(*storage.MySQLStorage) @@ -120,3 +120,28 @@ func GetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *er w.Write(data) return nil } + +// DeleteHandler deletes all data for a GUN. A 200 responses indicates success. +func DeleteHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError { + s := ctx.Value("versionStore") + store, ok := s.(*storage.MySQLStorage) + if !ok { + return &errors.HTTPError{ + HTTPStatus: http.StatusInternalServerError, + Code: 9999, + Err: fmt.Errorf("Version store not configured"), + } + } + vars := mux.Vars(r) + gun := vars["imageName"] + err := store.Delete(gun) + if err != nil { + logrus.Errorf("[Notary Server] 500 DELETE repository: %s", gun) + return &errors.HTTPError{ + HTTPStatus: http.StatusInternalServerError, + Code: 9999, + Err: err, + } + } + return nil +} diff --git a/server/server.go b/server/server.go index 9cf2e07746..49b8879017 100644 --- a/server/server.go +++ b/server/server.go @@ -107,6 +107,7 @@ func run(ctx context.Context, addr, tlsCertFile, tlsKeyFile string, trust signed // TODO (endophage): use correct regexes for image and tag names r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/{tufRole:(root|targets|timestamp|snapshot)}.json").Handler(hand(handlers.GetHandler, "pull")) r.Methods("POST").Path("/v2/{imageName:.*}/_trust/tuf/{tufRole:(root|targets|timestamp|snapshot)}.json").Handler(hand(handlers.UpdateHandler, "push", "pull")) + r.Methods("DELETE").Path("/v2/{imageName:.*}/_trust/tuf/").Handler(hand(handlers.DeleteHandler, "push", "pull")) svr := NewHTTPServer( http.Server{ diff --git a/server/storage/database.go b/server/storage/database.go index a4d20dfb38..f96acf4247 100644 --- a/server/storage/database.go +++ b/server/storage/database.go @@ -9,12 +9,12 @@ import ( // The database table must look like: // CREATE TABLE `tuf_files` ( // `id` INT AUTO_INCREMENT, -// `qdn` VARCHAR(255) NOT NULL +// `gun` VARCHAR(255) NOT NULL // `role` VARCHAR(255) NOT NULL // `version` INT // `data` LONGBLOB // PRIMARY KEY (`id`) -// UNIQUE INDEX (`qdn`, `role`, `version`) +// UNIQUE INDEX (`gun`, `role`, `version`) // ) DEFAULT CHARSET=utf8; type MySQLStorage struct { sql.DB @@ -28,25 +28,25 @@ func NewMySQLStorage(db *sql.DB) *MySQLStorage { // Update multiple TUF records in a single transaction. // Always insert a new row. The unique constraint will ensure there is only ever -func (vdb *MySQLStorage) UpdateCurrent(qdn, role string, version int, data []byte) error { - checkStmt := "SELECT count(*) FROM `tuf_files` WHERE `qdn`=? AND `role`=? AND `version`>=?;" - insertStmt := "INSERT INTO `tuf_files` (`qdn`, `role`, `version`, `data`) VALUES (?,?,?,?) ;" +func (db *MySQLStorage) UpdateCurrent(gun, role string, version int, data []byte) error { + checkStmt := "SELECT count(*) FROM `tuf_files` WHERE `gun`=? AND `role`=? AND `version`>=?;" + insertStmt := "INSERT INTO `tuf_files` (`gun`, `role`, `version`, `data`) VALUES (?,?,?,?) ;" // ensure immediately previous version exists - row := vdb.QueryRow(checkStmt, qdn, role, version) + row := db.QueryRow(checkStmt, gun, role, version) var exists int err := row.Scan(&exists) if err != nil { return err } if exists != 0 { - return fmt.Errorf("Attempting to write an old version for QDN: %s, role: %s, version: %d. A newer version is available.", qdn, role, version) + return fmt.Errorf("Attempting to write an old version for gun: %s, role: %s, version: %d. A newer version is available.", gun, role, version) } // attempt to insert. Due to race conditions with the check this could fail. // That's OK, we're doing first write wins. The client will be messaged it // needs to rebase. - _, err = vdb.Exec(insertStmt, qdn, role, version, data) + _, err = db.Exec(insertStmt, gun, role, version, data) if err != nil { return err } @@ -54,14 +54,14 @@ func (vdb *MySQLStorage) UpdateCurrent(qdn, role string, version int, data []byt } // Get a specific TUF record -func (vdb *MySQLStorage) GetCurrent(qdn, tufRole string) (data []byte, err error) { - stmt := "SELECT `data` FROM `tuf_files` WHERE `qdn`=? AND `role`=? ORDER BY `version` DESC LIMIT 1;" - rows, err := vdb.Query(stmt, qdn, tufRole) // this should be a QueryRow() +func (db *MySQLStorage) GetCurrent(gun, tufRole string) (data []byte, err error) { + stmt := "SELECT `data` FROM `tuf_files` WHERE `gun`=? AND `role`=? ORDER BY `version` DESC LIMIT 1;" + rows, err := db.Query(stmt, gun, tufRole) // this should be a QueryRow() if err != nil { return nil, err } defer rows.Close() - // unique constraint on (qdn, role) will ensure only one row is returned (or none if no match is found) + // unique constraint on (gun, role) will ensure only one row is returned (or none if no match is found) if !rows.Next() { return nil, nil } @@ -72,3 +72,9 @@ func (vdb *MySQLStorage) GetCurrent(qdn, tufRole string) (data []byte, err error } return data, nil } + +func (db *MySQLStorage) Delete(gun string) error { + stmt := "DELETE FROM `tuf_files` WHERE `gun`=?;" + _, err := db.Exec(stmt, gun) + return err +}