Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Define and implement an OpenAPI spec in the backend #141

Merged
merged 7 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading