Skip to content

Commit

Permalink
Define and implement an OpenAPI spec in the backend (#141)
Browse files Browse the repository at this point in the history
* Define and use an OpenAPI spec

Signed-off-by: Ali Ok <[email protected]>

* Add new codegen to update and verify scripts

Signed-off-by: Ali Ok <[email protected]>

* Update codegen

Signed-off-by: Ali Ok <[email protected]>

* goimports

Signed-off-by: Ali Ok <[email protected]>

* Fix URLs in tests and the plugin

Signed-off-by: Ali Ok <[email protected]>

* Make linter happier

Signed-off-by: Ali Ok <[email protected]>

* Fix scripts

Signed-off-by: Ali Ok <[email protected]>

---------

Signed-off-by: Ali Ok <[email protected]>
  • Loading branch information
aliok authored Jan 15, 2025
1 parent 6b00a80 commit a625a17
Show file tree
Hide file tree
Showing 23 changed files with 1,029 additions and 179 deletions.
75 changes: 75 additions & 0 deletions backends/pkg/reconciler/eventmesh/api.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 50 additions & 0 deletions backends/pkg/reconciler/eventmesh/authtokenmiddleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package eventmesh

import (
"context"
"net/http"
"strings"

"github.com/gorilla/mux"
)

type authTokenKey struct{}

const (
AuthTokenHeader = "Authorization"
BearerPrefix = "Bearer "
)

func AuthTokenMiddleware() mux.MiddlewareFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authTokenStr := r.Header.Get(AuthTokenHeader)
if authTokenStr != "" && strings.HasPrefix(authTokenStr, BearerPrefix) {
authTokenStr = strings.TrimPrefix(authTokenStr, BearerPrefix)
}

// set the token in the context
if authTokenStr == "" || strings.TrimSpace(authTokenStr) == "" {
http.Error(w, "missing auth token. set the 'Authorization: Bearer YOUR_KEY' header", http.StatusUnauthorized)
return
}

ctx := WithAuthToken(r.Context(), authTokenStr)

// Call the handler
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}

func WithAuthToken(ctx context.Context, authToken string) context.Context {
return context.WithValue(ctx, authTokenKey{}, authToken)
}

func GetAuthToken(ctx context.Context) (string, bool) {
token, ok := ctx.Value(authTokenKey{}).(string)
if token == "" {
return "", false
}
return token, ok
}
22 changes: 0 additions & 22 deletions backends/pkg/reconciler/eventmesh/broker.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,6 @@ import (
eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1"
)

// Broker is a simplified representation of a Knative Eventing Broker that is easier to consume by the Backstage plugin.
type Broker struct {
// Namespace of the broker
Namespace string `json:"namespace"`

// Name of the broker
Name string `json:"name"`

// UID of the broker
UID string `json:"uid"`

// Labels of the broker. These are passed as is.
Labels map[string]string `json:"labels,omitempty"`

// Annotations of the broker. These are passed as is, except that are filtered out by the FilterAnnotations function.
Annotations map[string]string `json:"annotations,omitempty"`

// ProvidedEventTypes is a list of event types that the broker provides.
// This is a list of strings, where each string is a "<namespace>/<name>" of the event type.
ProvidedEventTypes []string `json:"providedEventTypes,omitempty"`
}

// GetNamespacedName returns the name and namespace of the broker in the format "<namespace>/<name>"
func (b Broker) GetNamespacedName() string {
return NamespacedName(b.Namespace, b.Name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,96 +3,27 @@ package eventmesh
import (
"context"
"fmt"
"log"
"net/http"
"sort"

"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/client-go/rest"
"go.uber.org/zap"

eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1"
"knative.dev/eventing/pkg/client/clientset/versioned"
duckv1 "knative.dev/pkg/apis/duck/v1"
"knative.dev/pkg/logging"

"go.uber.org/zap"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1"

"k8s.io/apimachinery/pkg/util/json"
)

// EventMesh is the top-level struct that holds the event mesh data.
// It's the struct that's serialized and sent to the Backstage plugin.
type EventMesh struct {
// EventTypes is a list of all event types in the cluster.
// While we can embed the event types in the brokers, we keep them separate because
// not every event type is tied to a broker.
EventTypes []*EventType `json:"eventTypes"`

// Brokers is a list of all brokers in the cluster.
Brokers []*Broker `json:"brokers"`
}

// BackstageKubernetesIDLabel is the label that's used to identify Backstage resources.
// In Backstage Kubernetes plugin, a Backstage entity (e.g. a service) is tied to a Kubernetes resource
// using this label.
// see Backstage Kubernetes plugin for more details.
const BackstageKubernetesIDLabel = "backstage.io/kubernetes-id"

// HttpHandler is the HTTP handler that's used to serve the event mesh data.
type HttpHandler struct {
ctx context.Context
inClusterConfig *rest.Config
}

// This handler simply calls the event mesh builder and returns the result as JSON
func (h HttpHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
logger := logging.FromContext(h.ctx)

w.Header().Add("Content-Type", "application/json")

logger.Debugw("Handling request", "method", req.Method, "url", req.URL)

config := rest.CopyConfig(h.inClusterConfig)
authHeader := req.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "Authorization header is missing", http.StatusUnauthorized)
return
}
// header value is in this format: "Bearer <token>"
// we only need the token part
if len(authHeader) < 8 || authHeader[:7] != "Bearer " {
http.Error(w, "Invalid Authorization header. Should start with `Bearer `", http.StatusUnauthorized)
return
}
config.BearerToken = authHeader[7:]
clientset, err := versioned.NewForConfig(config)
if err != nil {
log.Fatalf("Error creating clientset: %v", err)
}

dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
log.Fatalf("Error creating dynamic client: %v", err)
}

eventMesh, err := BuildEventMesh(h.ctx, clientset, dynamicClient, logger)
if err != nil {
logger.Errorw("Error building event mesh", "error", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

err = json.NewEncoder(w).Encode(eventMesh)
if err != nil {
logger.Errorw("Error encoding event mesh", "error", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

// BuildEventMesh builds the event mesh data by fetching and converting the Kubernetes resources.
// The procedure is as follows:
// - Fetch the brokers and convert them to the representation that's consumed by the Backstage plugin.
Expand Down Expand Up @@ -124,8 +55,8 @@ func BuildEventMesh(ctx context.Context, clientset versioned.Interface, dynamicC

// register the event types in the brokers
for _, et := range convertedEventTypes {
if et.Reference != "" {
if br, ok := brokerMap[et.Reference]; ok {
if et.Reference != nil {
if br, ok := brokerMap[*et.Reference]; ok {
br.ProvidedEventTypes = append(br.ProvidedEventTypes, et.NamespacedName())
}
}
Expand Down Expand Up @@ -157,9 +88,18 @@ func BuildEventMesh(ctx context.Context, clientset versioned.Interface, dynamicC
}
}

outputEventTypes := make([]EventType, 0, len(convertedEventTypes))
for _, et := range convertedEventTypes {
outputEventTypes = append(outputEventTypes, *et)
}
outputBrokers := make([]Broker, 0, len(convertedBrokers))
for _, br := range convertedBrokers {
outputBrokers = append(outputBrokers, *br)
}

eventMesh := EventMesh{
EventTypes: convertedEventTypes,
Brokers: convertedBrokers,
EventTypes: outputEventTypes,
Brokers: outputBrokers,
}

return eventMesh, nil
Expand Down
Loading

0 comments on commit a625a17

Please sign in to comment.