Skip to content

Commit

Permalink
adds basic checking of subscription state
Browse files Browse the repository at this point in the history
  • Loading branch information
s-amann committed Apr 10, 2024
1 parent 1c115a4 commit 186bd52
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 15 deletions.
18 changes: 9 additions & 9 deletions frontend/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,41 @@ import (
"github.com/Azure/ARO-HCP/internal/api/subscription"
)

type cache struct {
type Cache struct {
cluster map[string]*api.HCPOpenShiftCluster
subscription map[string]*subscription.Subscription
}

// NewCache returns a new cache.
func NewCache() *cache {
return &cache{
func NewCache() *Cache {
return &Cache{
cluster: make(map[string]*api.HCPOpenShiftCluster),
subscription: make(map[string]*subscription.Subscription),
}
}

func (c *cache) GetCluster(id string) (*api.HCPOpenShiftCluster, bool) {
func (c *Cache) GetCluster(id string) (*api.HCPOpenShiftCluster, bool) {
cluster, found := c.cluster[id]
return cluster, found
}

func (c *cache) SetCluster(id string, cluster *api.HCPOpenShiftCluster) {
func (c *Cache) SetCluster(id string, cluster *api.HCPOpenShiftCluster) {
c.cluster[id] = cluster
}

func (c *cache) DeleteCluster(id string) {
func (c *Cache) DeleteCluster(id string) {
delete(c.cluster, id)
}

func (c *cache) GetSubscription(id string) (*subscription.Subscription, bool) {
func (c *Cache) GetSubscription(id string) (*subscription.Subscription, bool) {
subscription, found := c.subscription[id]
return subscription, found
}

func (c *cache) SetSubscription(id string, subscription *subscription.Subscription) {
func (c *Cache) SetSubscription(id string, subscription *subscription.Subscription) {
c.subscription[id] = subscription
}

func (c *cache) DeleteSubscription(id string) {
func (c *Cache) DeleteSubscription(id string) {
delete(c.subscription, id)
}
1 change: 1 addition & 0 deletions frontend/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const (
ContextKeyVersion
ContextKeyCorrelationData
ContextKeySystemData
ContextKeySubscriptionState

// Wildcard path segment names for request multiplexing
PageSegmentLocation = "location"
Expand Down
13 changes: 7 additions & 6 deletions frontend/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type Frontend struct {
logger *slog.Logger
listener net.Listener
server http.Server
cache cache
cache Cache
ready atomic.Value
done chan struct{}
}
Expand All @@ -59,6 +59,8 @@ func NewFrontend(logger *slog.Logger, listener net.Listener) *Frontend {
done: make(chan struct{}),
}

subscriptionStateMuxValidator := NewSubscriptionStateMuxValidator(&f.cache)

mux := NewMiddlewareMux(
MiddlewarePanic,
MiddlewareLogging,
Expand All @@ -69,11 +71,14 @@ func NewFrontend(logger *slog.Logger, listener net.Listener) *Frontend {
// Unauthenticated routes
mux.HandleFunc("/", f.NotFound)
mux.HandleFunc(MuxPattern(http.MethodGet, "healthz", "ready"), f.HealthzReady)
// TODO: determine where in the auth chain we should allow for this endpoint to be called by ARM
mux.HandleFunc(MuxPattern(http.MethodPut, PatternSubscriptions), f.ArmSubscriptionAction)

// Authenticated routes
postMuxMiddleware := NewMiddleware(
MiddlewareLoggingPostMux,
MiddlewareValidateAPIVersion)
MiddlewareValidateAPIVersion,
subscriptionStateMuxValidator.MiddlewareValidateSubscriptionState)
mux.Handle(
MuxPattern(http.MethodGet, PatternSubscriptions, PatternProviders),
postMuxMiddleware.HandlerFunc(f.ArmResourceListBySubscription))
Expand All @@ -98,7 +103,6 @@ func NewFrontend(logger *slog.Logger, listener net.Listener) *Frontend {
mux.Handle(
MuxPattern(http.MethodPost, PatternSubscriptions, PatternResourceGroups, PatternProviders, PatternResourceName, PatternActionName),
postMuxMiddleware.HandlerFunc(f.ArmResourceAction))
mux.Handle(MuxPattern(http.MethodPut, PatternSubscriptions), postMuxMiddleware.HandlerFunc(f.ArmSubscriptionAction))
f.server.Handler = mux

return f
Expand Down Expand Up @@ -281,9 +285,6 @@ func (f *Frontend) ArmResourceAction(writer http.ResponseWriter, request *http.R

func (f *Frontend) ArmSubscriptionAction(writer http.ResponseWriter, request *http.Request) {
ctx := request.Context()
logger := ctx.Value(ContextKeyLogger).(*slog.Logger)
versionedInterface := ctx.Value(ContextKeyVersion).(api.Version)
logger.Info(fmt.Sprintf("%s: ArmSubscriptionAction", versionedInterface))

body := ctx.Value(ContextKeyBody).([]byte)
var subscription subscription.Subscription
Expand Down
66 changes: 66 additions & 0 deletions frontend/middleware_validatesubscription.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package main

// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.

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

"github.com/Azure/ARO-HCP/internal/api"
"github.com/Azure/ARO-HCP/internal/api/arm"
"github.com/Azure/ARO-HCP/internal/api/subscription"
)

type SubscriptionStateMuxValidator struct {
cache *Cache
}

func NewSubscriptionStateMuxValidator(c *Cache) *SubscriptionStateMuxValidator {
return &SubscriptionStateMuxValidator{
cache: c,
}
}

func (s *SubscriptionStateMuxValidator) MiddlewareValidateSubscriptionState(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
subscriptionId := r.PathValue(strings.ToLower(PathSegmentSubscriptionID))
sub, exists := s.cache.GetSubscription(subscriptionId)
if subscriptionId == "" {
arm.WriteError(
w, http.StatusBadRequest,
arm.CloudErrorCodeInvalidParameter, "",
"The request is missing required parameter '%s'.",
PathSegmentSubscriptionID)
} else if !exists {
arm.WriteError(
w, http.StatusBadRequest,
arm.CloudErrorInvalidSubscriptionState, "",
"The subscription %s is not registered for this provider %s. Please re-register the subscription",
subscriptionId, api.ProviderNamespace)
} else if exists {
r = r.WithContext(context.WithValue(r.Context(), ContextKeySubscriptionState, sub.State))
switch sub.State {
case subscription.Registered:
next(w, r)
case subscription.Unregistered:
arm.WriteError(
w, http.StatusBadRequest,
arm.CloudErrorInvalidSubscriptionState, "",
"The subscription %s is not registered for this provider %s. Please re-register the subscription",
subscriptionId, api.ProviderNamespace)
case subscription.Warned:
// TODO: check request method
next(w, r)
case subscription.Suspended:
// TODO: check request method
next(w, r)
case subscription.Deleted:
arm.WriteError(
w, http.StatusBadRequest,
arm.CloudErrorInvalidSubscriptionState, "",
"The subscription %s is deleted and cannot be used to interact with %s",
subscriptionId, api.ProviderNamespace)
}
}
}
1 change: 1 addition & 0 deletions internal/api/arm/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const (
CloudErrorCodeInvalidResourceType = "InvalidResourceType"
CloudErrorCodeUnsupportedMediaType = "UnsupportedMediaType"
CloudErrorCodeNotFound = "NotFound"
CloudErrorInvalidSubscriptionState = "InvalidSubscriptionState"
)

// CloudError represents a complete resource provider error.
Expand Down

0 comments on commit 186bd52

Please sign in to comment.