Przeglądaj źródła

add endpoints for listing and reading events

Alexander Belanger 4 lat temu
rodzic
commit
3be6d8c248

+ 43 - 8
internal/models/events.go

@@ -38,14 +38,32 @@ type EventExternal struct {
 	OwnerType string `json:"owner_name"`
 	OwnerName string `json:"owner_type"`
 
-	EventType    string `json:"event_type"`
-	RefType      string `json:"ref_type"`
-	RefName      string `json:"ref_name"`
-	RefNamespace string `json:"ref_namespace"`
-	Message      string `json:"message"`
-	Reason       string `json:"reason"`
-	Timestamp    time.Time
-	Data         []byte `json:"data"`
+	EventType    string    `json:"event_type"`
+	RefType      string    `json:"resource_type"`
+	RefName      string    `json:"name"`
+	RefNamespace string    `json:"namespace"`
+	Message      string    `json:"message"`
+	Reason       string    `json:"reason"`
+	Data         []byte    `json:"data"`
+	Timestamp    time.Time `json:"timestamp"`
+}
+
+type EventExternalSimple struct {
+	ID uint `json:"id"`
+
+	ProjectID uint `json:"project_id"`
+	ClusterID uint `json:"cluster_id"`
+
+	OwnerType string `json:"owner_name"`
+	OwnerName string `json:"owner_type"`
+
+	EventType    string    `json:"event_type"`
+	RefType      string    `json:"resource_type"`
+	RefName      string    `json:"name"`
+	RefNamespace string    `json:"namespace"`
+	Message      string    `json:"message"`
+	Reason       string    `json:"reason"`
+	Timestamp    time.Time `json:"timestamp"`
 }
 
 // Externalize generates an external Event to be shared over REST
@@ -65,3 +83,20 @@ func (e *Event) Externalize() *EventExternal {
 		Data:         e.Data,
 	}
 }
+
+// Externalize generates an external Event to be shared over REST
+func (e *Event) ExternalizeSimple() *EventExternalSimple {
+	return &EventExternalSimple{
+		ID:           e.ID,
+		ProjectID:    e.ProjectID,
+		ClusterID:    e.ClusterID,
+		OwnerName:    e.OwnerName,
+		OwnerType:    e.OwnerType,
+		EventType:    e.EventType,
+		RefType:      e.RefType,
+		RefName:      e.RefName,
+		RefNamespace: e.RefNamespace,
+		Reason:       e.Reason,
+		Timestamp:    e.Timestamp,
+	}
+}

+ 7 - 5
internal/repository/event.go

@@ -4,12 +4,14 @@ import "github.com/porter-dev/porter/internal/models"
 
 // ListEventOpts are the options for listing events
 type ListEventOpts struct {
-	Limit int
-	Skip  int
-	Type  string
+	ClusterID uint `schema:"cluster_id"`
+
+	Limit int    `schema:"limit"`
+	Skip  int    `schema:"skip"`
+	Type  string `schema:"type"`
 
 	// can only be "timestamp" for now
-	SortBy string
+	SortBy string `schema:"sort_by"`
 
 	// Decrypt is whether to decrypt the underlying Data field, which may not be desired
 	// for basic list operations
@@ -20,7 +22,7 @@ type ListEventOpts struct {
 // Event model
 type EventRepository interface {
 	CreateEvent(event *models.Event) (*models.Event, error)
-	ReadEvent(id uint) (*models.Event, error)
+	ReadEvent(id uint, projID uint, clusterID uint) (*models.Event, error)
 	ListEventsByProjectID(projectID uint, opts *ListEventOpts) ([]*models.Event, error)
 	DeleteEvent(id uint) error
 }

+ 8 - 3
internal/repository/gorm/event.go

@@ -40,12 +40,17 @@ func (repo *EventRepository) CreateEvent(
 
 // ReadEvent finds an event by id
 func (repo *EventRepository) ReadEvent(
-	id uint,
+	id, projID, clusterID uint,
 ) (*models.Event, error) {
 	event := &models.Event{}
 
 	// preload Clusters association
-	if err := repo.db.Where("id = ?", id).First(&event).Error; err != nil {
+	if err := repo.db.Where(
+		"id = ? AND project_id = ? AND cluster_id = ?",
+		id,
+		projID,
+		clusterID,
+	).First(&event).Error; err != nil {
 		return nil, err
 	}
 
@@ -72,7 +77,7 @@ func (repo *EventRepository) ListEventsByProjectID(
 
 	events := []*models.Event{}
 
-	query := repo.db.Where("project_id = ?", projectID)
+	query := repo.db.Where("project_id = ? AND cluster_id = ?", projectID, opts.ClusterID)
 
 	if listOpts.Type != "" {
 		query = repo.db.Where(

+ 15 - 12
internal/repository/gorm/event_test.go

@@ -41,7 +41,7 @@ func TestCreateEvent(t *testing.T) {
 		t.Fatalf("%v\n", err)
 	}
 
-	event, err = tester.repo.Event.ReadEvent(event.Model.ID)
+	event, err = tester.repo.Event.ReadEvent(event.Model.ID, 1, 1)
 
 	if err != nil {
 		t.Fatalf("%v\n", err)
@@ -74,9 +74,10 @@ func TestListEventsByProjectIDWithLimit(t *testing.T) {
 	defer cleanup(tester, t)
 
 	testListEventsByProjectID(tester, t, &repository.ListEventOpts{
-		Limit:   10,
-		Type:    "node",
-		Decrypt: true,
+		ClusterID: 1,
+		Limit:     10,
+		Type:      "node",
+		Decrypt:   true,
 	}, tester.initEvents[50:60])
 }
 
@@ -94,9 +95,10 @@ func TestListEventsByProjectIDWithSkip(t *testing.T) {
 	defer cleanup(tester, t)
 
 	testListEventsByProjectID(tester, t, &repository.ListEventOpts{
-		Limit:   25,
-		Skip:    10,
-		Decrypt: true,
+		ClusterID: 1,
+		Limit:     25,
+		Skip:      10,
+		Decrypt:   true,
 	}, tester.initEvents[10:35])
 }
 
@@ -114,11 +116,12 @@ func TestListEventsByProjectIDWithSortBy(t *testing.T) {
 	defer cleanup(tester, t)
 
 	testListEventsByProjectID(tester, t, &repository.ListEventOpts{
-		Limit:   1,
-		Skip:    0,
-		Type:    "node",
-		Decrypt: true,
-		SortBy:  "timestamp",
+		ClusterID: 1,
+		Limit:     1,
+		Skip:      0,
+		Type:      "node",
+		Decrypt:   true,
+		SortBy:    "timestamp",
 	}, tester.initEvents[99:])
 }
 

+ 97 - 0
server/api/event_handler.go

@@ -7,7 +7,11 @@ import (
 	"strconv"
 
 	"github.com/go-chi/chi"
+	"github.com/gorilla/schema"
 	"github.com/porter-dev/porter/internal/forms"
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+	"gorm.io/gorm"
 )
 
 // HandleCreateEvent creates a new event in a project
@@ -62,3 +66,96 @@ func (app *App) HandleCreateEvent(w http.ResponseWriter, r *http.Request) {
 
 	w.WriteHeader(http.StatusCreated)
 }
+
+// HandleListEvents lists the events that match certain conditions in a project
+func (app *App) HandleListEvents(w http.ResponseWriter, r *http.Request) {
+	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
+
+	if err != nil || projID == 0 {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	vals, err := url.ParseQuery(r.URL.RawQuery)
+
+	opts := &repository.ListEventOpts{}
+
+	decoder := schema.NewDecoder()
+
+	if err := decoder.Decode(opts, vals); err != nil {
+		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
+			Code:   ErrReleaseReadData,
+			Errors: []string{"bad request"},
+		}, w)
+	}
+
+	// handle write to the database
+	events, err := app.Repo.Event.ListEventsByProjectID(uint(projID), opts)
+
+	if err != nil {
+		app.handleErrorDataWrite(err, w)
+		return
+	}
+
+	eventExts := make([]*models.EventExternalSimple, 0)
+
+	for _, event := range events {
+		eventExts = append(eventExts, event.ExternalizeSimple())
+	}
+
+	w.WriteHeader(http.StatusOK)
+
+	if err := json.NewEncoder(w).Encode(eventExts); err != nil {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+}
+
+// HandleListEvents lists the events that match certain conditions in a project
+func (app *App) HandleGetEvent(w http.ResponseWriter, r *http.Request) {
+	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
+
+	if err != nil || projID == 0 {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	vals, err := url.ParseQuery(r.URL.RawQuery)
+
+	clusterID, err := strconv.ParseUint(vals["cluster_id"][0], 10, 64)
+
+	if err != nil {
+		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
+			Code:   ErrReleaseReadData,
+			Errors: []string{"cluster not found"},
+		}, w)
+	}
+
+	eventID, err := strconv.ParseUint(chi.URLParam(r, "event_id"), 0, 64)
+
+	if err != nil || projID == 0 {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	event, err := app.Repo.Event.ReadEvent(uint(eventID), uint(projID), uint(clusterID))
+
+	if err != nil {
+		if err == gorm.ErrRecordNotFound {
+			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
+			return
+		}
+
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	eventExt := event.Externalize()
+
+	w.WriteHeader(http.StatusOK)
+
+	if err := json.NewEncoder(w).Encode(eventExt); err != nil {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+}

+ 28 - 0
server/router/router.go

@@ -421,6 +421,34 @@ func New(a *api.App) *chi.Mux {
 				),
 			)
 
+			r.Method(
+				"GET",
+				"/projects/{project_id}/events",
+				auth.DoesUserHaveProjectAccess(
+					auth.DoesUserHaveClusterAccess(
+						requestlog.NewHandler(a.HandleListEvents, l),
+						mw.URLParam,
+						mw.QueryParam,
+					),
+					mw.URLParam,
+					mw.AdminAccess,
+				),
+			)
+
+			r.Method(
+				"GET",
+				"/projects/{project_id}/events/{event_id}",
+				auth.DoesUserHaveProjectAccess(
+					auth.DoesUserHaveClusterAccess(
+						requestlog.NewHandler(a.HandleGetEvent, l),
+						mw.URLParam,
+						mw.QueryParam,
+					),
+					mw.URLParam,
+					mw.AdminAccess,
+				),
+			)
+
 			// /api/projects/{project_id}/invites routes
 			r.Method(
 				"POST",