Skip to content

Commit

Permalink
Add http endpoints to get status for requets (#28)
Browse files Browse the repository at this point in the history
* Add http endpoints to get status for requets

Requests are asynchronous. Anarchy needs to be able to query status of a
specific request identified by an ID.

Switch to nanoid for convenience as they are URL-friendly.

* Fix name account->request in swagger

* go fmt
  • Loading branch information
fridim authored Jul 19, 2023
1 parent f3d323d commit a38d28a
Show file tree
Hide file tree
Showing 9 changed files with 288 additions and 7 deletions.
3 changes: 1 addition & 2 deletions cmd/sandbox-api/account_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"github.com/rhpds/sandbox/internal/models"

"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/render"
)

Expand Down Expand Up @@ -171,7 +170,7 @@ func (h *BaseHandler) LifeCycleAccountHandler(action string) http.HandlerFunc {
// by the swagger openAPI spec.
// kind := chi.URLParam(r, "kind")

reqId := middleware.GetReqID(r.Context())
reqId := GetReqID(r.Context())

// Get the account from DynamoDB
sandbox, err := h.accountProvider.FetchByName(accountName)
Expand Down
84 changes: 82 additions & 2 deletions cmd/sandbox-api/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"github.com/getkin/kin-openapi/openapi3"
oarouters "github.com/getkin/kin-openapi/routers"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/jwtauth/v5"
"github.com/go-chi/render"
"github.com/jackc/pgx/v4"
Expand Down Expand Up @@ -296,7 +295,7 @@ func (h *BaseHandler) DeletePlacementHandler(w http.ResponseWriter, r *http.Requ
func (h *BaseHandler) LifeCyclePlacementHandler(action string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
serviceUuid := chi.URLParam(r, "uuid")
reqId := middleware.GetReqID(r.Context())
reqId := GetReqID(r.Context())

placement, err := models.GetPlacementByServiceUuid(h.dbpool, serviceUuid)

Expand Down Expand Up @@ -727,3 +726,84 @@ func (h *BaseHandler) InvalidateTokenHandler(w http.ResponseWriter, r *http.Requ
Message: "Token successfully invalidated",
})
}

// GetStatusRequestHandler returns the status of a request
func (h *BaseHandler) GetStatusRequestHandler(w http.ResponseWriter, r *http.Request) {
RequestID := chi.URLParam(r, "id")

if RequestID == "" {
w.WriteHeader(http.StatusBadRequest)
render.Render(w, r, &v1.Error{
HTTPStatusCode: http.StatusBadRequest,
Message: "Missing request id",
})
log.Logger.Error("Missing request id")
return
}

// Get the request from the DB
job, err := models.GetLifecyclePlacementJobByRequestID(h.dbpool, RequestID)

if err != nil {
if err == pgx.ErrNoRows {
// No placement request found, try any resource request
job, err := models.GetLifecycleResourceJobByRequestID(h.dbpool, RequestID)
if err != nil {
if err == pgx.ErrNoRows {

w.WriteHeader(http.StatusNotFound)
render.Render(w, r, &v1.Error{
HTTPStatusCode: http.StatusNotFound,
Message: "Request not found",
})
log.Logger.Info("Request not found")
return
}
w.WriteHeader(http.StatusInternalServerError)
render.Render(w, r, &v1.Error{
HTTPStatusCode: http.StatusInternalServerError,
Message: "Error getting request",
})
log.Logger.Error("Error getting request", "error", err)
return
}

// If it's a resource request, just return the status
w.WriteHeader(http.StatusOK)
render.Render(w, r, &v1.LifecycleRequestResponse{
HTTPStatusCode: http.StatusOK,
RequestID: RequestID,
Status: job.Status,
})
return
}

w.WriteHeader(http.StatusInternalServerError)
render.Render(w, r, &v1.Error{
HTTPStatusCode: http.StatusInternalServerError,
Message: "Error getting request",
})
log.Logger.Error("Error getting request", "error", err)
return
}

// Get the status of the request
status, err := job.GlobalStatus()

if err != nil {
w.WriteHeader(http.StatusInternalServerError)
render.Render(w, r, &v1.Error{
HTTPStatusCode: http.StatusInternalServerError,
Message: "Error getting status",
})
log.Logger.Error("Error getting status", "error", err)
return
}

w.WriteHeader(http.StatusOK)
render.Render(w, r, &v1.LifecycleRequestResponse{
HTTPStatusCode: http.StatusOK,
RequestID: RequestID,
Status: status,
})
}
3 changes: 2 additions & 1 deletion cmd/sandbox-api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func main() {
// Middlewares
// ---------------------------------------------------------------------
router.Use(middleware.CleanPath)
router.Use(middleware.RequestID)
router.Use(ShortRequestID)
router.Use(httplog.RequestLogger(logger))
router.Use(middleware.Heartbeat("/ping"))
// Set Content-Type header to application/json for all responses
Expand Down Expand Up @@ -196,6 +196,7 @@ func main() {
r.Put("/api/v1/placements/{uuid}/start", baseHandler.LifeCyclePlacementHandler("start"))
r.Put("/api/v1/placements/{uuid}/status", baseHandler.LifeCyclePlacementHandler("status"))
r.Get("/api/v1/placements/{uuid}/status", baseHandler.GetStatusPlacementHandler)
r.Get("/api/v1/requests/{id}/status", baseHandler.GetStatusRequestHandler)
})

// ---------------------------------------------------------------------
Expand Down
54 changes: 53 additions & 1 deletion cmd/sandbox-api/middlewares.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/go-chi/render"
"github.com/jackc/pgx/v4"
"github.com/lestrrat-go/jwx/v2/jwt"
"github.com/matoous/go-nanoid/v2"

"github.com/rhpds/sandbox/internal/api/v1"
"github.com/rhpds/sandbox/internal/log"
Expand Down Expand Up @@ -219,7 +220,8 @@ func (h *BaseHandler) AuthenticatorLogin(next http.Handler) http.Handler {
// response for any unverified tokens and passes the good ones through. It's just fine
func AuthenticatorAccess(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token, claims, err := jwtauth.FromContext(r.Context())
ctx := r.Context()
token, claims, err := jwtauth.FromContext(ctx)

if err != nil {
w.WriteHeader(http.StatusUnauthorized)
Expand Down Expand Up @@ -252,3 +254,53 @@ func AuthenticatorAccess(next http.Handler) http.Handler {
next.ServeHTTP(w, r)
})
}

// RequestID

var RequestIDHeader = "X-Request-Id"

// Key to use when setting the request ID.
type ctxKeyRequestID int

// RequestIDKey is the key that holds the unique request ID in a request context.
const RequestIDKey ctxKeyRequestID = 0

// GetReqID returns a request ID from the given context if one is present.
// Returns the empty string if a request ID cannot be found.
func GetReqID(ctx context.Context) string {
if ctx == nil {
return ""
}
if reqID, ok := ctx.Value(RequestIDKey).(string); ok {
return reqID
}
return ""
}

// ShortRequestID is a middleware that injects a Short request ID into the context of each request.
func ShortRequestID(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
requestID := r.Header.Get(RequestIDHeader)
if requestID == "" {

var err error
requestID, err = gonanoid.New()
log.Logger.Info("Generating new request ID", "requestID", requestID)

if err != nil {
log.Logger.Error("Error generating request ID", "error", err)
w.WriteHeader(http.StatusInternalServerError)
render.Render(w, r, &v1.Error{
HTTPStatusCode: http.StatusInternalServerError,
Message: "Error generating request ID",
})
return
}

}
ctx = context.WithValue(ctx, RequestIDKey, requestID)
next.ServeHTTP(w, r.WithContext(ctx))
}
return http.HandlerFunc(fn)
}
46 changes: 45 additions & 1 deletion docs/api-reference/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,47 @@ paths:
schema:
$ref: "#/components/schemas/Error"

/requests/{id}/status:
parameters:
- in: header
name: Authorization
description: Access JTW Token
required: true
schema:
type: string
example: Bearer <ACCESS_TOKEN>
- name: id
in: path
required: true
description: The id of the request
schema:
type: string
get:
tags:
- requests
operationId: getStatusRequest
summary: Get status of a request
description: |-
Returns status of a request.
responses:
'200':
description: Request status
content:
application/json:
schema:
$ref: "#/components/schemas/LifecycleResponse"
'404':
description: Request not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
default:
description: getStatusRequest unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/admin/jwt:
parameters:
- in: header
Expand Down Expand Up @@ -1193,10 +1234,13 @@ components:
properties:
request_id:
type: string
example: hostname/icXUKzyr5U-000002
example: F8HWO8Bo79n1I_DPQPIEw
message:
type: string
example: "Request created"
status:
type: string
example: "running"
AccountStatus:
type: object
properties:
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
github.com/go-chi/render v1.0.2
github.com/jackc/pgx/v4 v4.18.1
github.com/lestrrat-go/jwx/v2 v2.0.9
github.com/matoous/go-nanoid/v2 v2.0.0
github.com/prometheus/client_golang v1.15.0
github.com/sosedoff/ansible-vault-go v0.2.0
golang.org/x/exp v0.0.0-20230420155640-133eef4313cb
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U=
github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0=
github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
Expand Down
1 change: 1 addition & 0 deletions internal/api/v1/v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type LifecycleRequestResponse struct {
HTTPStatusCode int `json:"http_code,omitempty"` // http response status code
Message string `json:"message"`
RequestID string `json:"request_id,omitempty"`
Status string `json:"status,omitempty"`
}

type AccountStatusResponse struct {
Expand Down
Loading

0 comments on commit a38d28a

Please sign in to comment.