feat: [CDE-93]: List API to fetch gitspace events. (#2176)

* feat: [CDE-93]: Adding openapi spec.
* feat: [CDE-93]: List API to fetch gitspace events.
This commit is contained in:
Dhruv Dhruv 2024-07-08 08:29:56 +00:00 committed by Harness
parent 0fffff1416
commit f3d3f7392a
14 changed files with 278 additions and 84 deletions

View File

@ -25,6 +25,7 @@ type Controller struct {
gitspaceConfigStore store.GitspaceConfigStore
gitspaceInstanceStore store.GitspaceInstanceStore
spaceStore store.SpaceStore
gitspaceEventStore store.GitspaceEventStore
}
// TODO Stubbed Impl
@ -34,6 +35,7 @@ func NewController(
gitspaceConfigStore store.GitspaceConfigStore,
gitspaceInstanceStore store.GitspaceInstanceStore,
spaceStore store.SpaceStore,
gitspaceEventStore store.GitspaceEventStore,
) *Controller {
return &Controller{
authorizer: authorizer,
@ -41,5 +43,6 @@ func NewController(
gitspaceConfigStore: gitspaceConfigStore,
gitspaceInstanceStore: gitspaceInstanceStore,
spaceStore: spaceStore,
gitspaceEventStore: gitspaceEventStore,
}
}

View File

@ -0,0 +1,105 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gitspace
import (
"context"
"fmt"
"time"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
var eventMessageMap map[enum.GitspaceEventType]string
func init() {
eventMessageMap = eventsMessageMapping()
}
func (c *Controller) GetEvents(
ctx context.Context,
session *auth.Session,
spaceRef string,
identifier string,
page int,
limit int,
) ([]*types.GitspaceEventResponse, int, error) {
space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return nil, 0, fmt.Errorf("failed to find space: %w", err)
}
err = apiauth.CheckGitspace(ctx, c.authorizer, session, space.Path, identifier, enum.PermissionGitspaceView)
if err != nil {
return nil, 0, fmt.Errorf("failed to authorize: %w", err)
}
filter := &types.GitspaceEventFilter{}
filter.QueryKey = identifier
filter.Page = page
filter.Size = limit
events, count, err := c.gitspaceEventStore.List(ctx, filter)
if err != nil {
return nil, 0, fmt.Errorf("failed to list gitspace events for identifier %s: %w", identifier, err)
}
var result = make([]*types.GitspaceEventResponse, len(events))
for index, event := range events {
gitspaceEventResponse := &types.GitspaceEventResponse{
GitspaceEvent: *event,
Message: eventMessageMap[event.Event],
EventTime: time.UnixMilli(event.Created).Format(time.RFC3339)}
result[index] = gitspaceEventResponse
}
return result, count, nil
}
func eventsMessageMapping() map[enum.GitspaceEventType]string {
var gitspaceConfigsMap = make(map[enum.GitspaceEventType]string)
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStart] = "Starting Gitspace..."
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStartCompleted] = "Started Gitspace"
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStartFailed] = "Starting Gitspace Failed"
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStop] = "Stopping Gitspace"
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStopCompleted] = "Stopped Gitspace"
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStopFailed] = "Stopping Gitspace Failed"
gitspaceConfigsMap[enum.GitspaceEventTypeInfraProvisioningStart] = "Provisioning Infrastructure..."
gitspaceConfigsMap[enum.GitspaceEventTypeInfraProvisioningCompleted] = "Provisioning Infrastructure Completed"
gitspaceConfigsMap[enum.GitspaceEventTypeInfraProvisioningFailed] = "Provisioning Infrastructure Failed"
gitspaceConfigsMap[enum.GitspaceEventTypeInfraUnprovisioningStart] = "Unprovisioning Infrastructure..."
gitspaceConfigsMap[enum.GitspaceEventTypeInfraUnprovisioningCompleted] = "Unprovisioning Infrastructure Completed"
gitspaceConfigsMap[enum.GitspaceEventTypeInfraUnprovisioningFailed] = "Unprovisioning Infrastructure Failed"
gitspaceConfigsMap[enum.GitspaceEventTypeAgentConnectStart] = "Connecting to the gitspace agent..."
gitspaceConfigsMap[enum.GitspaceEventTypeAgentConnectCompleted] = "Connected to the gitspace agent"
gitspaceConfigsMap[enum.GitspaceEventTypeAgentConnectFailed] = "Failed connecting to the gitspace agent"
gitspaceConfigsMap[enum.GitspaceEventTypeAgentGitspaceCreationStart] = "Setting up the gitspace..."
gitspaceConfigsMap[enum.GitspaceEventTypeAgentGitspaceCreationCompleted] = "Successfully setup the gitspace"
gitspaceConfigsMap[enum.GitspaceEventTypeAgentGitspaceCreationFailed] = "Failed to setup the gitspace"
gitspaceConfigsMap[enum.GitspaceEventTypeAgentGitspaceStateReportRunning] = "Gitspace is running"
gitspaceConfigsMap[enum.GitspaceEventTypeAgentGitspaceStateReportStopped] = "Gitspace is stopped"
gitspaceConfigsMap[enum.GitspaceEventTypeAgentGitspaceStateReportUnknown] = "Gitspace is in unknown state"
gitspaceConfigsMap[enum.GitspaceEventTypeAgentGitspaceStateReportError] = "Gitspace has an error"
return gitspaceConfigsMap
}

View File

@ -32,6 +32,7 @@ func ProvideController(
configStore store.GitspaceConfigStore,
instanceStore store.GitspaceInstanceStore,
spaceStore store.SpaceStore,
eventStore store.GitspaceEventStore,
) *Controller {
return NewController(authorizer, resourceStore, configStore, instanceStore, spaceStore)
return NewController(authorizer, resourceStore, configStore, instanceStore, spaceStore, eventStore)
}

View File

@ -0,0 +1,53 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gitspace
import (
"net/http"
"github.com/harness/gitness/app/api/controller/gitspace"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/app/paths"
)
func HandleGetEvents(gitspaceCtrl *gitspace.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
gitspaceRefFromPath, err := request.GetGitspaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
spaceRef, gitspaceIdentifier, err := paths.DisectLeaf(gitspaceRefFromPath)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
page := request.ParsePage(r)
limit := request.ParseLimit(r)
events, count, err := gitspaceCtrl.GetEvents(ctx, session, spaceRef, gitspaceIdentifier, page, limit)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.Pagination(r, w, page, limit, count)
render.JSON(w, http.StatusOK, events)
}
}

View File

@ -47,6 +47,10 @@ type gitspacesListRequest struct {
paginationRequest
}
type gitspaceEventsListRequest struct {
paginationRequest
}
func gitspaceOperations(reflector *openapi3.Reflector) {
opCreate := openapi3.Operation{}
opCreate.WithTags("gitspaces")
@ -109,4 +113,15 @@ func gitspaceOperations(reflector *openapi3.Reflector) {
_ = reflector.SetJSONResponse(&opList, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opList, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodGet, "/gitspaces", opList)
opEventList := openapi3.Operation{}
opEventList.WithTags("gitspaces")
opEventList.WithSummary("List gitspace events")
opEventList.WithMapOfAnything(map[string]interface{}{"operationId": "listGitspaceEvents"})
_ = reflector.SetRequest(&opList, new(gitspaceEventsListRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&opEventList, new([]*types.GitspaceEventResponse), http.StatusOK)
_ = reflector.SetJSONResponse(&opEventList, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opEventList, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opEventList, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodGet, "/gitspaces/{gitspace_identifier}/events", opEventList)
}

View File

@ -31,11 +31,11 @@ const (
type (
GitspaceEventPayload struct {
EntityID int64 `json:"entity_id,omitempty"`
EntityIdentifier string `json:"entity_identifier,omitempty"`
EntityType enum.GitspaceEntityType `json:"entity_type,omitempty"`
EventType enum.GitspaceEventType `json:"event_type,omitempty"`
Created int64 `json:"created,omitempty"`
EntityID int64 `json:"entity_id,omitempty"`
QueryKey string `json:"query_key,omitempty"`
EntityType enum.GitspaceEntityType `json:"entity_type,omitempty"`
EventType enum.GitspaceEventType `json:"event_type,omitempty"`
Created int64 `json:"created,omitempty"`
}
)

View File

@ -707,6 +707,7 @@ func setupGitspaces(r chi.Router, gitspacesCtrl *gitspace.Controller) {
r.Post("/action", handlergitspace.HandleAction(gitspacesCtrl))
r.Delete("/", handlergitspace.HandleDeleteConfig(gitspacesCtrl))
r.Patch("/", handlergitspace.HandleUpdateConfig(gitspacesCtrl))
r.Get("/events", handlergitspace.HandleGetEvents(gitspacesCtrl))
})
})
}

View File

@ -28,11 +28,11 @@ func (s *Service) handleGitspaceEvent(
event *events.Event[*gitspaceevents.GitspaceEventPayload],
) error {
gitspaceEvent := &types.GitspaceEvent{
Event: event.Payload.EventType,
EntityID: event.Payload.EntityID,
EntityIdentifier: event.Payload.EntityIdentifier,
EntityType: event.Payload.EntityType,
Created: event.Payload.Created,
Event: event.Payload.EventType,
EntityID: event.Payload.EntityID,
QueryKey: event.Payload.QueryKey,
EntityType: event.Payload.EntityType,
Created: event.Payload.Created,
}
err := s.gitspaceEventStore.Create(ctx, gitspaceEvent)

View File

@ -924,8 +924,8 @@ type (
// Create creates a new record for the given gitspace event.
Create(ctx context.Context, gitspaceEvent *types.GitspaceEvent) error
// List returns all events for the given query filter.
List(ctx context.Context, filter *types.GitspaceEventFilter) ([]*types.GitspaceEvent, error)
// List returns all events and count for the given query filter.
List(ctx context.Context, filter *types.GitspaceEventFilter) ([]*types.GitspaceEvent, int, error)
// FindLatestByTypeAndGitspaceConfigID returns the latest gitspace event for the given config ID and event type
// where the entity type is gitspace config.

View File

@ -24,6 +24,7 @@ import (
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/Masterminds/squirrel"
"github.com/jmoiron/sqlx"
)
@ -48,12 +49,12 @@ type gitspaceEventStore struct {
}
type gitspaceEvent struct {
ID int64 `db:"geven_id"`
Event enum.GitspaceEventType `db:"geven_event"`
Created int64 `db:"geven_created"`
EntityType enum.GitspaceEntityType `db:"geven_entity_type"`
EntityIdentifier string `db:"geven_entity_uid"`
EntityID int64 `db:"geven_entity_id"`
ID int64 `db:"geven_id"`
Event enum.GitspaceEventType `db:"geven_event"`
Created int64 `db:"geven_created"`
EntityType enum.GitspaceEntityType `db:"geven_entity_type"`
QueryKey string `db:"geven_entity_uid"` // TODO: change to query_key
EntityID int64 `db:"geven_entity_id"`
}
func NewGitspaceEventStore(db *sqlx.DB) store.GitspaceEventStore {
@ -93,7 +94,7 @@ func (g gitspaceEventStore) Create(ctx context.Context, gitspaceEvent *types.Git
gitspaceEvent.Event,
gitspaceEvent.Created,
gitspaceEvent.EntityType,
gitspaceEvent.EntityIdentifier,
gitspaceEvent.QueryKey,
gitspaceEvent.EntityID,
).
Suffix("RETURNING " + gitspaceEventIDColumn)
@ -111,23 +112,72 @@ func (g gitspaceEventStore) Create(ctx context.Context, gitspaceEvent *types.Git
func (g gitspaceEventStore) List(
ctx context.Context,
filter *types.GitspaceEventFilter,
) ([]*types.GitspaceEvent, error) {
stmt := database.Builder.
) ([]*types.GitspaceEvent, int, error) {
queryStmt := database.Builder.
Select(gitspaceEventsColumnsWithID).
From(gitspaceEventsTable).
Where("geven_entity_id = $1", filter.EntityID).
Where("geven_entity_type = $2", filter.EntityType)
sql, args, err := stmt.ToSql()
From(gitspaceEventsTable)
queryStmt = g.setQueryFilter(queryStmt, filter)
queryStmt = g.setPaginationFilter(queryStmt, filter)
sql, args, err := queryStmt.ToSql()
if err != nil {
return nil, fmt.Errorf("failed to convert squirrel builder to sql: %w", err)
return nil, 0, fmt.Errorf("failed to convert squirrel builder to sql: %w", err)
}
db := dbtx.GetAccessor(ctx, g.db)
gitspaceEventEntities := make([]*gitspaceEvent, 0)
if err = db.SelectContext(ctx, gitspaceEventEntities, sql, args...); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to find gitspaceEvent")
return nil, 0, database.ProcessSQLErrorf(ctx, err, "Failed to find gitspaceEvent")
}
countStmt := database.Builder.
Select("count(*)").
From(gitspaceEventsTable)
countStmt = g.setQueryFilter(countStmt, filter)
sql, args, err = countStmt.ToSql()
if err != nil {
return nil, 0, fmt.Errorf("failed to convert squirrel builder to sql: %w", err)
}
var count int
err = db.QueryRowContext(ctx, sql, args...).Scan(&count)
if err != nil {
return nil, 0, database.ProcessSQLErrorf(ctx, err, "Failed executing custom count query")
}
gitspaceEvents := g.mapGitspaceEvents(gitspaceEventEntities)
return gitspaceEvents, nil
return gitspaceEvents, count, nil
}
func (g gitspaceEventStore) setQueryFilter(
stmt squirrel.SelectBuilder,
filter *types.GitspaceEventFilter,
) squirrel.SelectBuilder {
if filter.QueryKey != "" {
stmt = stmt.Where(squirrel.Eq{"geven_entity_uid": filter.QueryKey})
}
if filter.EntityType != "" {
stmt = stmt.Where(squirrel.Eq{"geven_entity_type": filter.EntityType})
}
if filter.EntityID != 0 {
stmt = stmt.Where(squirrel.Eq{"geven_entity_id": filter.EntityID})
}
return stmt
}
func (g gitspaceEventStore) setPaginationFilter(
stmt squirrel.SelectBuilder,
filter *types.GitspaceEventFilter,
) squirrel.SelectBuilder {
offset := (filter.Page - 1) * filter.Size
stmt = stmt.Offset(uint64(offset)).Limit(uint64(filter.Size))
return stmt
}
func (g gitspaceEventStore) mapGitspaceEvents(gitspaceEventEntities []*gitspaceEvent) []*types.GitspaceEvent {
@ -141,10 +191,10 @@ func (g gitspaceEventStore) mapGitspaceEvents(gitspaceEventEntities []*gitspaceE
func (g gitspaceEventStore) mapGitspaceEvent(event *gitspaceEvent) *types.GitspaceEvent {
return &types.GitspaceEvent{
Event: event.Event,
Created: event.Created,
EntityType: event.EntityType,
EntityIdentifier: event.EntityIdentifier,
EntityID: event.EntityID,
Event: event.Event,
Created: event.Created,
EntityType: event.EntityType,
QueryKey: event.QueryKey,
EntityID: event.EntityID,
}
}

View File

@ -64,6 +64,7 @@ var WireSet = wire.NewSet(
ProvideInfraProviderResourceStore,
ProvideGitspaceConfigStore,
ProvideGitspaceInstanceStore,
ProvideGitspaceEventStore,
)
// migrator is helper function to set up the database by performing automated
@ -283,3 +284,8 @@ func ProvidePublicAccessStore(db *sqlx.DB) store.PublicAccessStore {
func ProvidePublicKeyStore(db *sqlx.DB) store.PublicKeyStore {
return NewPublicKeyStore(db)
}
// ProvideGitspaceEventStore provides a gitspace event store.
func ProvideGitspaceEventStore(db *sqlx.DB) store.GitspaceEventStore {
return NewGitspaceEventStore(db)
}

View File

@ -311,7 +311,8 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
keywordsearchController := keywordsearch2.ProvideController(authorizer, searcher, repoController, spaceController)
infraProviderResourceStore := database.ProvideInfraProviderResourceStore(db)
gitspaceInstanceStore := database.ProvideGitspaceInstanceStore(db)
gitspaceController := gitspace.ProvideController(authorizer, infraProviderResourceStore, gitspaceConfigStore, gitspaceInstanceStore, spaceStore)
gitspaceEventStore := database.ProvideGitspaceEventStore(db)
gitspaceController := gitspace.ProvideController(authorizer, infraProviderResourceStore, gitspaceConfigStore, gitspaceInstanceStore, spaceStore, gitspaceEventStore)
migrateController := migrate.ProvideController(authorizer, principalStore)
apiHandler := router.ProvideAPIHandler(ctx, config, authenticator, repoController, reposettingsController, executionController, logsController, spaceController, pipelineController, secretController, triggerController, connectorController, templateController, pluginController, pullreqController, webhookController, githookController, gitInterface, serviceaccountController, controller, principalController, checkController, systemController, uploadController, keywordsearchController, gitspaceController, migrateController)
gitHandler := router.ProvideGitHandler(provider, authenticator, repoController)

View File

@ -51,8 +51,6 @@ var gitspaceEventTypes = []GitspaceEventType{
GitspaceEventTypeAgentGitspaceStateReportUnknown,
}
var eventsMessageMap = eventsMessageMapping()
const (
// Start action events.
GitspaceEventTypeGitspaceActionStart GitspaceEventType = "gitspace_action_start"
@ -90,44 +88,3 @@ const (
GitspaceEventTypeAgentGitspaceStateReportStopped GitspaceEventType = "agent_gitspace_state_report_stopped"
GitspaceEventTypeAgentGitspaceStateReportUnknown GitspaceEventType = "agent_gitspace_state_report_unknown"
)
func (e GitspaceEventType) GetValue() string {
return eventsMessageMap[e]
}
// TODO: Move eventsMessageMapping() to controller.
func eventsMessageMapping() map[GitspaceEventType]string {
var gitspaceConfigsMap = make(map[GitspaceEventType]string)
gitspaceConfigsMap[GitspaceEventTypeGitspaceActionStart] = "Starting Gitspace..."
gitspaceConfigsMap[GitspaceEventTypeGitspaceActionStartCompleted] = "Started Gitspace"
gitspaceConfigsMap[GitspaceEventTypeGitspaceActionStartFailed] = "Starting Gitspace Failed"
gitspaceConfigsMap[GitspaceEventTypeGitspaceActionStop] = "Stopping Gitspace"
gitspaceConfigsMap[GitspaceEventTypeGitspaceActionStopCompleted] = "Stopped Gitspace"
gitspaceConfigsMap[GitspaceEventTypeGitspaceActionStopFailed] = "Stopping Gitspace Failed"
gitspaceConfigsMap[GitspaceEventTypeInfraProvisioningStart] = "Provisioning Infrastructure..."
gitspaceConfigsMap[GitspaceEventTypeInfraProvisioningCompleted] = "Provisioning Infrastructure Completed"
gitspaceConfigsMap[GitspaceEventTypeInfraProvisioningFailed] = "Provisioning Infrastructure Failed"
gitspaceConfigsMap[GitspaceEventTypeInfraUnprovisioningStart] = "Unprovisioning Infrastructure..."
gitspaceConfigsMap[GitspaceEventTypeInfraUnprovisioningCompleted] = "Unprovisioning Infrastructure Completed"
gitspaceConfigsMap[GitspaceEventTypeInfraUnprovisioningFailed] = "Unprovisioning Infrastructure Failed"
gitspaceConfigsMap[GitspaceEventTypeAgentConnectStart] = "Connecting to the gitspace agent..."
gitspaceConfigsMap[GitspaceEventTypeAgentConnectCompleted] = "Connected to the gitspace agent"
gitspaceConfigsMap[GitspaceEventTypeAgentConnectFailed] = "Failed connecting to the gitspace agent"
gitspaceConfigsMap[GitspaceEventTypeAgentGitspaceCreationStart] = "Setting up the gitspace..."
gitspaceConfigsMap[GitspaceEventTypeAgentGitspaceCreationCompleted] = "Successfully setup the gitspace"
gitspaceConfigsMap[GitspaceEventTypeAgentGitspaceCreationFailed] = "Failed to setup the gitspace"
gitspaceConfigsMap[GitspaceEventTypeAgentGitspaceStateReportRunning] = "Gitspace is running"
gitspaceConfigsMap[GitspaceEventTypeAgentGitspaceStateReportStopped] = "Gitspace is stopped"
gitspaceConfigsMap[GitspaceEventTypeAgentGitspaceStateReportUnknown] = "Gitspace is in unknown state"
gitspaceConfigsMap[GitspaceEventTypeAgentGitspaceStateReportError] = "Gitspace has an error"
return gitspaceConfigsMap
}

View File

@ -17,12 +17,12 @@ package types
import "github.com/harness/gitness/types/enum"
type GitspaceEvent struct {
ID int64 `json:"-"`
Event enum.GitspaceEventType `json:"event,omitempty"`
EntityID int64 `json:"-"`
EntityIdentifier string `json:"entity_identifier,omitempty"`
EntityType enum.GitspaceEntityType `json:"entity_type,omitempty"`
Created int64 `json:"timestamp,omitempty"`
ID int64 `json:"-"`
Event enum.GitspaceEventType `json:"event,omitempty"`
EntityID int64 `json:"-"`
QueryKey string `json:"query_key,omitempty"`
EntityType enum.GitspaceEntityType `json:"entity_type,omitempty"`
Created int64 `json:"timestamp,omitempty"`
}
type GitspaceEventResponse struct {
@ -32,6 +32,8 @@ type GitspaceEventResponse struct {
}
type GitspaceEventFilter struct {
Pagination
QueryKey string
EntityID int64
EntityType enum.GitspaceEntityType
}