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

Net-2707/list resource endpoint #18444

Merged
merged 1 commit into from
Aug 15, 2023
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
92 changes: 78 additions & 14 deletions internal/resource/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,24 @@ import (
"github.com/hashicorp/consul/proto-public/pbresource"
)

const (
HeaderConsulToken = "x-consul-token"
HeaderConsistencyMode = "x-consul-consistency-mode"
)

func NewHandler(
client pbresource.ResourceServiceClient,
registry resource.Registry,
parseToken func(req *http.Request, token *string),
logger hclog.Logger) http.Handler {
mux := http.NewServeMux()
for _, t := range registry.Types() {
// Individual Resource Endpoints.
prefix := strings.ToLower(fmt.Sprintf("/%s/%s/%s/", t.Type.Group, t.Type.GroupVersion, t.Type.Kind))
// List Endpoint
base := strings.ToLower(fmt.Sprintf("/%s/%s/%s", t.Type.Group, t.Type.GroupVersion, t.Type.Kind))
mux.Handle(base, http.StripPrefix(base, &listHandler{t, client, parseToken, logger}))

// Individual Resource Endpoints
prefix := strings.ToLower(fmt.Sprintf("%s/", base))
logger.Info("Registered resource endpoint", "endpoint", prefix)
mux.Handle(prefix, http.StripPrefix(prefix, &resourceHandler{t, client, parseToken, logger}))
}
Expand All @@ -55,7 +64,7 @@ type resourceHandler struct {
func (h *resourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var token string
h.parseToken(r, &token)
ctx := metadata.AppendToOutgoingContext(r.Context(), "x-consul-token", token)
ctx := metadata.AppendToOutgoingContext(r.Context(), HeaderConsulToken, token)
switch r.Method {
case http.MethodPut:
h.handleWrite(w, r, ctx)
Expand Down Expand Up @@ -106,7 +115,7 @@ func (h *resourceHandler) handleWrite(w http.ResponseWriter, r *http.Request, ct
},
})
if err != nil {
handleResponseError(err, w, h)
handleResponseError(err, w, h.logger)
return
}

Expand All @@ -133,7 +142,7 @@ func (h *resourceHandler) handleRead(w http.ResponseWriter, r *http.Request, ctx
},
})
if err != nil {
handleResponseError(err, w, h)
handleResponseError(err, w, h.logger)
return
}

Expand All @@ -158,7 +167,7 @@ func (h *resourceHandler) handleDelete(w http.ResponseWriter, r *http.Request, c
Version: params["version"],
})
if err != nil {
handleResponseError(err, w, h)
handleResponseError(err, w, h.logger)
return
}
w.WriteHeader(http.StatusNoContent)
Expand All @@ -181,11 +190,12 @@ func parseParams(r *http.Request) (tenancy *pbresource.Tenancy, params map[strin
params = make(map[string]string)
params["resourceName"] = resourceName
params["version"] = query.Get("version")
params["namePrefix"] = query.Get("name_prefix")
if _, ok := query["consistent"]; ok {
params["consistent"] = "true"
}

return
return tenancy, params
}

func jsonMarshal(res *pbresource.Resource) ([]byte, error) {
Expand All @@ -203,28 +213,82 @@ func jsonMarshal(res *pbresource.Resource) ([]byte, error) {
return json.MarshalIndent(stuff, "", " ")
}

func handleResponseError(err error, w http.ResponseWriter, h *resourceHandler) {
func handleResponseError(err error, w http.ResponseWriter, logger hclog.Logger) {
if e, ok := status.FromError(err); ok {
switch e.Code() {
case codes.InvalidArgument:
w.WriteHeader(http.StatusBadRequest)
h.logger.Info("User has mal-formed request", "error", err)
logger.Info("User has mal-formed request", "error", err)
case codes.NotFound:
w.WriteHeader(http.StatusNotFound)
h.logger.Info("Received error from resource service: Not found", "error", err)
logger.Info("Received error from resource service: Not found", "error", err)
case codes.PermissionDenied:
w.WriteHeader(http.StatusForbidden)
h.logger.Info("Received error from resource service: User not authenticated", "error", err)
logger.Info("Received error from resource service: User not authenticated", "error", err)
case codes.Aborted:
w.WriteHeader(http.StatusConflict)
h.logger.Info("Received error from resource service: the request conflict with the current state of the target resource", "error", err)
logger.Info("Received error from resource service: the request conflict with the current state of the target resource", "error", err)
default:
w.WriteHeader(http.StatusInternalServerError)
h.logger.Error("Received error from resource service", "error", err)
logger.Error("Received error from resource service", "error", err)
}
} else {
w.WriteHeader(http.StatusInternalServerError)
h.logger.Error("Received error from resource service: not able to parse error returned", "error", err)
logger.Error("Received error from resource service: not able to parse error returned", "error", err)
}
w.Write([]byte(err.Error()))
}

type listHandler struct {
reg resource.Registration
client pbresource.ResourceServiceClient
parseToken func(req *http.Request, token *string)
logger hclog.Logger
}

func (h *listHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}

var token string
h.parseToken(r, &token)
ctx := metadata.AppendToOutgoingContext(r.Context(), HeaderConsulToken, token)

tenancyInfo, params := parseParams(r)
if params["consistent"] == "true" {
ctx = metadata.AppendToOutgoingContext(ctx, HeaderConsistencyMode, "consistent")
}

rsp, err := h.client.List(ctx, &pbresource.ListRequest{
Type: h.reg.Type,
Tenancy: tenancyInfo,
NamePrefix: params["namePrefix"],
})
if err != nil {
handleResponseError(err, w, h.logger)
return
}

output := make([]json.RawMessage, len(rsp.Resources))
for idx, res := range rsp.Resources {
b, err := jsonMarshal(res)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
h.logger.Error("Failed to unmarshal GRPC resource response", "error", err)
return
}
output[idx] = b
}

b, err := json.MarshalIndent(struct {
Resources []json.RawMessage `json:"resources"`
}{output}, "", " ")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
h.logger.Error("Failed to correctly format the list response", "error", err)
return
}
w.Write(b)
}
Loading