Skip to content

Commit

Permalink
Merge pull request #9 from carverauto/new/rbac
Browse files Browse the repository at this point in the history
New/rbac
  • Loading branch information
mfreeman451 authored Oct 6, 2024
2 parents 2af90e0 + 6d1d287 commit c79f2cb
Show file tree
Hide file tree
Showing 32 changed files with 1,903 additions and 121 deletions.
50 changes: 50 additions & 0 deletions .github/.testcoverage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# (mandatory)
# Path to coverprofile file (output of `go test -coverprofile` command).
#
# For cases where there are many coverage profiles, such as when running
# unit tests and integration tests separately, you can combine all those
# profiles into one. In this case, the profile should have a comma-separated list
# of profile files, e.g., 'cover_unit.out,cover_integration.out'.
profile: cover.out

# (optional; but recommended to set)
# When specified reported file paths will not contain local prefix in the output
local-prefix: "github.com/carverauto/eventrunner"

# Holds coverage thresholds percentages, values should be in range [0-100]
threshold:
# (optional; default 0)
# The minimum coverage that each file should have
file: 70

# (optional; default 0)
# The minimum coverage that each package should have
package: 80

# (optional; default 0)
# The minimum total coverage project should have
total: 95

# Holds regexp rules which will override thresholds for matched files or packages
# using their paths.
#
# First rule from this list that matches file or package is going to apply
# new threshold to it. If project has multiple rules that match same path,
# override rules should be listed in order from specific to more general rules.
override:
# Increase coverage threshold to 100% for `foo` package
# (default is 80, as configured above in this example)
- threshold: 100
path: ^pkg/lib/foo$

# Holds regexp rules which will exclude matched files or packages
# from coverage statistics
exclude:
# Exclude files or packages matching their paths
paths:
- \.pb\.go$ # excludes all protobuf generated files
- ^pkg/bar # exclude package `pkg/bar`

# NOTES:
# - symbol `/` in all path regexps will be replaced by current OS file path separator
# to properly work on Windows
46 changes: 23 additions & 23 deletions .github/workflows/go-coverage.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
name: Go test coverage check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3

- name: generate test coverage
run: go test ./... -coverprofile=./cover.out -covermode=atomic -coverpkg=./...

- name: check test coverage
uses: vladopajic/go-test-coverage@v2
with:
# Configure action using config file (option 1)
config: ./.testcoverage.yml

# Configure action by specifying input parameters individually (option 2).
# If you are using config file (option 1) you shouldn't use these parameters, however
# specifing these action parameters will override appropriate config values.
profile: cover.out
local-prefix: github.com/carverauto/eventrunner
threshold-file: 80
threshold-package: 80
threshold-total: 95
name: test
on: [push]
permissions:
contents: write
jobs:
test:
name: test
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: setup go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: test
run: make test
- name: check test coverage
uses: vladopajic/go-test-coverage@v2
with:
config: ./.github/.testcoverage.yml
git-branch: badges
git-token: ${{ github.ref_name == 'main' && secrets.GITHUB_TOKEN || '' }}
29 changes: 11 additions & 18 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
@@ -1,26 +1,19 @@
name: golangci-lint
on:
push:
branches:
- main
- master
pull_request:

permissions:
contents: read
# Optional: allow read access to pull request. Use with `only-new-issues` option.
# pull-requests: read

name: lint
on: [push]
jobs:
golangci:
lint:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- name: checkout
uses: actions/checkout@v4
- name: setup go
uses: actions/setup-go@v5
with:
go-version: stable
go-version-file: go.mod
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: v1.60
version: v1.60.3
- name: go mod tidy check
uses: katexochen/go-tidy-check@v2
37 changes: 37 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
GO ?= go
GOBIN ?= $$($(GO) env GOPATH)/bin
GOLANGCI_LINT ?= $(GOBIN)/golangci-lint
GOLANGCI_LINT_VERSION ?= v1.60.3

# Code tidy
.PHONY: tidy
tidy:
go mod tidy
go fmt ./...

.PHONY: get-golangcilint
get-golangcilint:
test -f $(GOLANGCI_LINT) || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $$($(GO) env GOPATH)/bin $(GOLANGCI_LINT_VERSION)

# Runs lint on entire repo
.PHONY: lint
lint: get-golangcilint
$(GOLANGCI_LINT) run ./...

# Runs tests on entire repo
.PHONY: test
test:
go test -timeout=3s -race -count=10 -failfast -shuffle=on -short ./... -coverprofile=./cover.short.profile -covermode=atomic -coverpkg=./...
go test -timeout=10s -race -count=1 -failfast -shuffle=on ./... -coverprofile=./cover.long.profile -covermode=atomic -coverpkg=./...

# Runs test coverage check
.PHONY: check-coverage
check-coverage: test
go run ./main.go --config=./.github/.testcoverage.yml

# View coverage profile
.PHONY: view-coverage
view-coverage:
go test ./... -coverprofile=./cover.all.profile -covermode=atomic -coverpkg=./...
go tool cover -html=cover.all.profile -o=cover.html
xdg-open cover.html
35 changes: 35 additions & 0 deletions cmd/api/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package main

import (
"github.com/carverauto/eventrunner/cmd/api/migrations"

Check failure on line 4 in cmd/api/main.go

View workflow job for this annotation

GitHub Actions / lint

could not import github.com/carverauto/eventrunner/cmd/api/migrations (-: # github.com/carverauto/eventrunner/cmd/api/migrations
"github.com/carverauto/eventrunner/pkg/api/handlers"

Check failure on line 5 in cmd/api/main.go

View workflow job for this annotation

GitHub Actions / lint

could not import github.com/carverauto/eventrunner/pkg/api/handlers (-: # github.com/carverauto/eventrunner/pkg/api/handlers
"github.com/carverauto/eventrunner/pkg/api/middleware"
"gofr.dev/pkg/gofr"
"gofr.dev/pkg/gofr/datasource/mongo"
)

func main() {
app := gofr.New()

// Set up MongoDB
db := mongo.New(mongo.Config{URI: "mongodb://localhost:27017", Database: "eventrunner"})
app.AddMongo(db)

// Run migrations
app.Migrate(migrations.All())

// Set up routes
tenantHandler := &handlers.TenantHandler{}
userHandler := &handlers.UserHandler{}

// Tenant routes (protected by API key)
app.POST("/tenants", tenantHandler.Create, middleware.AuthenticateAPIKey)
app.GET("/tenants", tenantHandler.GetAll, middleware.AuthenticateAPIKey)

// User routes (protected by API key and role-based access)
app.POST("/tenants/{tenant_id}/users", userHandler.Create, middleware.AuthenticateAPIKey, middleware.RequireRole("admin"))
app.GET("/tenants/{tenant_id}/users", userHandler.GetAll, middleware.AuthenticateAPIKey, middleware.RequireRole("admin", "user"))

// Run the application
app.Run()
}
12 changes: 12 additions & 0 deletions cmd/api/migrations/all.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package migrations

import (
"gofr.dev/pkg/gofr/migration"
)

func All() map[int64]migration.Migrate {
return map[int64]migration.Migrate{
20240226153000: createCollections(),

Check failure on line 9 in cmd/api/migrations/all.go

View workflow job for this annotation

GitHub Actions / lint

undefined: createCollections

Check failure on line 9 in cmd/api/migrations/all.go

View workflow job for this annotation

GitHub Actions / lint

undefined: createCollections
20240226153100: createIndexes(),

Check failure on line 10 in cmd/api/migrations/all.go

View workflow job for this annotation

GitHub Actions / lint

undefined: createIndexes (typecheck)

Check failure on line 10 in cmd/api/migrations/all.go

View workflow job for this annotation

GitHub Actions / lint

undefined: createIndexes) (typecheck)
}
}
6 changes: 6 additions & 0 deletions cmd/event-ingest/configs/.env
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,9 @@ NATS_CONSUMER=events
NATS_MAX_WAIT=10s
NATS_MAX_PULL_WAIT=10
NATS_BATCH_SIZE=100
GRPC_SERVER_ADDRESS=
KEYCLOAK_URL=https://keycloak.threadr.ai
KEYCLOAK_REALM=NGS
OAUTH_CLIENT_ID=eventrunner
OAUTH_CLIENT_SECRET=secret
TOKEN_INTROSPECT_URL=https://keycloak.threadr.ai/realms/CarverAuto/protocol/openid-connect/token/introspect
138 changes: 60 additions & 78 deletions cmd/event-ingest/main.go
Original file line number Diff line number Diff line change
@@ -1,99 +1,81 @@
package main

import (
"encoding/json"
"fmt"
"os"
"strings"
"time"

"github.com/carverauto/gofr-nats"
cloudevents "github.com/cloudevents/sdk-go/v2"
"github.com/google/uuid"
"context"
"log"

"github.com/carverauto/eventrunner/pkg/api/middleware"
"github.com/carverauto/eventrunner/pkg/config"
customctx "github.com/carverauto/eventrunner/pkg/context"
"github.com/carverauto/eventrunner/pkg/eventingest"
"gofr.dev/pkg/gofr"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)

type EventRouter struct {
app *gofr.App
natsClient *nats.PubSubWrapper
}

func NewEventRouter() *EventRouter {
func main() {
app := gofr.New()

subjects := strings.Split(os.Getenv("NATS_SUBJECTS"), ",")
// Load OAuth configuration
oauthConfig := config.LoadOAuthConfig(app)

natsClient := nats.New(&nats.Config{
Server: os.Getenv("PUBSUB_BROKER"),
Stream: nats.StreamConfig{
Stream: os.Getenv("NATS_STREAM"),
Subjects: subjects,
},
MaxWait: 5 * time.Second,
BatchSize: 100,
MaxPullWait: 10,
Consumer: os.Getenv("NATS_CONSUMER"),
CredsFile: os.Getenv("NATS_CREDS_FILE"),
})
natsClient.UseLogger(app.Logger)
natsClient.UseMetrics(app.Metrics())
natsClient.Connect()

app.AddPubSub(natsClient)

return &EventRouter{
app: app,
natsClient: natsClient,
}
}

func (er *EventRouter) Start() {
er.app.Subscribe("raw_events", er.handleRawEvent)
er.app.Run()
}

func (er *EventRouter) handleRawEvent(c *gofr.Context) error {
var rawEvent map[string]interface{}
if err := c.Bind(&rawEvent); err != nil {
return err
// Initialize JWT middleware
jwtMiddleware, err := middleware.NewJWTMiddleware(context.Background(), oauthConfig)
if err != nil {
log.Fatalf("Failed to initialize JWT middleware: %v", err)
}

event := cloudevents.NewEvent()
event.SetID(uuid.New().String())
event.SetSource("event-router")
// Set up gRPC connection to API
grpcServerAddress := app.Config.Get("GRPC_SERVER_ADDRESS")

eventType, ok := rawEvent["type"].(string)
if !ok {
return fmt.Errorf("missing or invalid event type")
conn, err := grpc.Dial(grpcServerAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("Failed to connect to gRPC server: %v", err)
}
event.SetType(eventType)

tenantID, ok := rawEvent["tenant_id"].(string)
if !ok {
return fmt.Errorf("missing or invalid tenant_id")
}
event.SetExtension("tenantid", tenantID)
defer conn.Close()

err := event.SetData(cloudevents.ApplicationJSON, rawEvent)
if err != nil {
return err
}
// Create gRPC event forwarder
eventForwarder := eventingest.NewGRPCEventForwarder(conn)

eventJSON, err := json.Marshal(event)
if err != nil {
return err
}
// Create and set up HTTP server
httpServer := eventingest.NewHTTPServer(app, eventForwarder)

// Route to appropriate consumer queue based on event type
consumerQueue := "events." + eventType
if err := er.natsClient.Publish(c.Context, consumerQueue, eventJSON); err != nil {
return err
}
// Register routes with middleware chain
app.POST("/events", combineMiddleware(
jwtMiddleware.Validate,
middleware.AuthenticateAPIKey,
middleware.RequireRole("admin", "event_publisher"),
func(cc *customctx.Context) (interface{}, error) {
return httpServer.HandleEvent(cc)
},
))

return nil
// Run the application
app.Run()
}

func main() {
router := NewEventRouter()
router.Start()
// combineMiddleware chains multiple middleware functions together
func combineMiddleware(middlewares ...interface{}) gofr.Handler {
return func(c *gofr.Context) (interface{}, error) {
cc := customctx.NewCustomContext(c)

var handler func(*customctx.Context) (interface{}, error)

// Apply middlewares in reverse order
for i := len(middlewares) - 1; i >= 0; i-- {
switch m := middlewares[i].(type) {
case func(*customctx.Context) (interface{}, error):
handler = m
case func(func(*customctx.Context) (interface{}, error)) func(*customctx.Context) (interface{}, error):
handler = m(handler)
case func(gofr.Handler) gofr.Handler:
return m(func(*gofr.Context) (interface{}, error) {
return handler(cc)
})(c)
}
}

return handler(cc)
}
}
Loading

0 comments on commit c79f2cb

Please sign in to comment.