From 70502658c86b7792d807d30e95184cde190ca543 Mon Sep 17 00:00:00 2001 From: Stefan Benten Date: Mon, 18 May 2020 19:36:09 +0200 Subject: [PATCH] satellite/admin: add delete project endpoint (#3888) --- satellite/admin/README.md | 4 +++ satellite/admin/project.go | 50 ++++++++++++++++++++++++++ satellite/admin/project_test.go | 62 +++++++++++++++++++++++++++++++-- satellite/admin/server.go | 4 +++ 4 files changed, 118 insertions(+), 2 deletions(-) diff --git a/satellite/admin/README.md b/satellite/admin/README.md index a8ef716f4..0e3c242e3 100644 --- a/satellite/admin/README.md +++ b/satellite/admin/README.md @@ -54,6 +54,10 @@ Updates usage limit for a project. Updates rate limit for a project. +## DELETE /api/project/{project-id} + +Deletes the project. + ## POST /api/project Adds a project for specific user. diff --git a/satellite/admin/project.go b/satellite/admin/project.go index d4f1f92df..de6034563 100644 --- a/satellite/admin/project.go +++ b/satellite/admin/project.go @@ -14,7 +14,9 @@ import ( "github.com/gorilla/mux" "github.com/gorilla/schema" + "storj.io/common/macaroon" "storj.io/common/memory" + "storj.io/common/storj" "storj.io/common/uuid" "storj.io/storj/satellite/console" ) @@ -285,3 +287,51 @@ func (server *Server) addProject(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write(data) // nothing to do with the error response, probably the client requesting disappeared } + +func (server *Server) deleteProject(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + vars := mux.Vars(r) + projectUUIDString, ok := vars["project"] + if !ok { + http.Error(w, "project-uuid missing", http.StatusBadRequest) + return + } + + projectUUID, err := uuid.FromString(projectUUIDString) + if err != nil { + http.Error(w, fmt.Sprintf("invalid project-uuid: %v", err), http.StatusBadRequest) + return + } + + if err := r.ParseForm(); err != nil { + http.Error(w, fmt.Sprintf("invalid form: %v", err), http.StatusBadRequest) + return + } + + buckets, err := server.db.Buckets().ListBuckets(ctx, projectUUID, storj.BucketListOptions{Limit: 1, Direction: storj.Forward}, macaroon.AllowedBuckets{All: true}) + if err != nil { + http.Error(w, fmt.Sprintf("unable to list buckets: %v", err), http.StatusInternalServerError) + return + } + if len(buckets.Items) > 0 { + http.Error(w, fmt.Sprintf("buckets still exist"), http.StatusConflict) + return + } + + keys, err := server.db.Console().APIKeys().GetPagedByProjectID(ctx, projectUUID, console.APIKeyCursor{Limit: 1, Page: 1}) + if err != nil { + http.Error(w, fmt.Sprintf("unable to list api-keys: %v", err), http.StatusInternalServerError) + return + } + if keys.TotalCount > 0 { + http.Error(w, fmt.Sprintf("api-keys still exist"), http.StatusConflict) + return + } + + err = server.db.Console().Projects().Delete(ctx, projectUUID) + if err != nil { + http.Error(w, fmt.Sprintf("unable to delete project: %v", err), http.StatusInternalServerError) + return + } +} diff --git a/satellite/admin/project_test.go b/satellite/admin/project_test.go index 93d298299..d4c152eff 100644 --- a/satellite/admin/project_test.go +++ b/satellite/admin/project_test.go @@ -15,10 +15,13 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap" + "storj.io/common/macaroon" + "storj.io/common/storj" "storj.io/common/testcontext" "storj.io/common/uuid" "storj.io/storj/private/testplanet" "storj.io/storj/satellite" + "storj.io/storj/satellite/console" ) func TestAPI(t *testing.T) { @@ -32,8 +35,8 @@ func TestAPI(t *testing.T) { }, }, }, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) { - satellite := planet.Satellites[0] - address := satellite.Admin.Admin.Listener.Addr() + sat := planet.Satellites[0] + address := sat.Admin.Admin.Listener.Addr() project := planet.Uplinks[0].Projects[0] link := "http://" + address.String() + "/api/project/" + project.ID.String() + "/limit" @@ -144,6 +147,61 @@ func TestAddProject(t *testing.T) { }) } +func TestDeleteProject(t *testing.T) { + testplanet.Run(t, testplanet.Config{ + SatelliteCount: 1, + StorageNodeCount: 0, + UplinkCount: 1, + Reconfigure: testplanet.Reconfigure{ + Satellite: func(log *zap.Logger, index int, config *satellite.Config) { + config.Admin.Address = "127.0.0.1:0" + }, + }, + }, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) { + address := planet.Satellites[0].Admin.Admin.Listener.Addr() + projectID := planet.Uplinks[0].Projects[0].ID + + // Ensure there are no buckets left + buckets, err := planet.Satellites[0].DB.Buckets().ListBuckets(ctx, projectID, storj.BucketListOptions{Limit: 1, Direction: storj.Forward}, macaroon.AllowedBuckets{All: true}) + require.NoError(t, err) + require.Len(t, buckets.Items, 0) + + apikeys, err := planet.Satellites[0].DB.Console().APIKeys().GetPagedByProjectID(ctx, projectID, console.APIKeyCursor{ + Page: 1, + Limit: 2, + Search: "", + }) + require.NoError(t, err) + require.Len(t, apikeys.APIKeys, 1) + + // the deletion with an existing API key should fail + req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("http://"+address.String()+"/api/project/%s", projectID), nil) + require.NoError(t, err) + req.Header.Set("Authorization", "very-secret-token") + + response, err := http.DefaultClient.Do(req) + require.NoError(t, err) + require.NoError(t, response.Body.Close()) + require.Equal(t, http.StatusConflict, response.StatusCode) + + err = planet.Satellites[0].DB.Console().APIKeys().Delete(ctx, apikeys.APIKeys[0].ID) + require.NoError(t, err) + + req, err = http.NewRequest(http.MethodDelete, fmt.Sprintf("http://"+address.String()+"/api/project/%s", projectID), nil) + require.NoError(t, err) + req.Header.Set("Authorization", "very-secret-token") + + response, err = http.DefaultClient.Do(req) + require.NoError(t, err) + require.NoError(t, response.Body.Close()) + require.Equal(t, http.StatusOK, response.StatusCode) + + project, err := planet.Satellites[0].DB.Console().Projects().Get(ctx, projectID) + require.Error(t, err) + require.Nil(t, project) + }) +} + func assertGet(t *testing.T, link string, expected string) { t.Helper() diff --git a/satellite/admin/server.go b/satellite/admin/server.go index 2504d6bb2..28204f2fa 100644 --- a/satellite/admin/server.go +++ b/satellite/admin/server.go @@ -18,6 +18,7 @@ import ( "storj.io/common/errs2" "storj.io/storj/satellite/accounting" "storj.io/storj/satellite/console" + "storj.io/storj/satellite/metainfo" ) // Config defines configuration for debug server. @@ -33,6 +34,8 @@ type DB interface { ProjectAccounting() accounting.ProjectAccounting // Console returns database for satellite console Console() console.DB + // Buckets returns database for satellite buckets + Buckets() metainfo.BucketsDB } // Server provides endpoints for debugging. @@ -64,6 +67,7 @@ func NewServer(log *zap.Logger, listener net.Listener, db DB, config Config) *Se server.mux.HandleFunc("/api/user/{useremail}", server.userInfo).Methods("GET") server.mux.HandleFunc("/api/project/{project}/limit", server.getProjectLimit).Methods("GET") server.mux.HandleFunc("/api/project/{project}/limit", server.putProjectLimit).Methods("PUT", "POST") + server.mux.HandleFunc("/api/project/{project}", server.deleteProject).Methods("DELETE") server.mux.HandleFunc("/api/project", server.addProject).Methods("POST") return server