hashicorp-copywrite[bot] 5fb9df1640
[COMPLIANCE] License changes (#18443)
* Adding explicit MPL license for sub-package

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Adding explicit MPL license for sub-package

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Updating the license from MPL to Business Source License

Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at <Blog URL>, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl.

* add missing license headers

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

---------

Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
2023-08-11 09:12:13 -04:00

174 lines
4.9 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package http
import (
"context"
"encoding/json"
"fmt"
"net/http"
"path"
"strings"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/known/anypb"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/proto-public/pbresource"
)
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))
logger.Info("Registered resource endpoint", "endpoint", prefix)
mux.Handle(prefix, http.StripPrefix(prefix, &resourceHandler{t, client, parseToken, logger}))
}
return mux
}
type writeRequest struct {
Metadata map[string]string `json:"metadata"`
Data json.RawMessage `json:"data"`
Owner *pbresource.ID `json:"owner"`
}
type resourceHandler struct {
reg resource.Registration
client pbresource.ResourceServiceClient
parseToken func(req *http.Request, token *string)
logger hclog.Logger
}
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)
switch r.Method {
case http.MethodPut:
h.handleWrite(w, r, ctx)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
}
func (h *resourceHandler) handleWrite(w http.ResponseWriter, r *http.Request, ctx context.Context) {
var req writeRequest
// convert req body to writeRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Request body didn't follow schema."))
}
// convert data struct to proto message
data := h.reg.Proto.ProtoReflect().New().Interface()
if err := protojson.Unmarshal(req.Data, data); err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Request body didn't follow schema."))
}
// proto message to any
anyProtoMsg, err := anypb.New(data)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
h.logger.Error("Failed to convert proto message to any type", "error", err)
return
}
tenancyInfo, resourceName, version := checkURL(r)
rsp, err := h.client.Write(ctx, &pbresource.WriteRequest{
Resource: &pbresource.Resource{
Id: &pbresource.ID{
Type: h.reg.Type,
Tenancy: tenancyInfo,
Name: resourceName,
},
Owner: req.Owner,
Version: version,
Metadata: req.Metadata,
Data: anyProtoMsg,
},
})
if err != nil {
handleResponseError(err, w, h)
return
}
output, err := jsonMarshal(rsp.Resource)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
h.logger.Error("Failed to unmarshal GRPC resource response", "error", err)
return
}
w.Write(output)
}
func checkURL(r *http.Request) (tenancy *pbresource.Tenancy, resourceName string, version string) {
params := r.URL.Query()
tenancy = &pbresource.Tenancy{
Partition: params.Get("partition"),
PeerName: params.Get("peer_name"),
Namespace: params.Get("namespace"),
}
resourceName = path.Base(r.URL.Path)
if resourceName == "." || resourceName == "/" {
resourceName = ""
}
version = params.Get("version")
return
}
func jsonMarshal(res *pbresource.Resource) ([]byte, error) {
output, err := protojson.Marshal(res)
if err != nil {
return nil, err
}
var stuff map[string]any
if err := json.Unmarshal(output, &stuff); err != nil {
return nil, err
}
delete(stuff["data"].(map[string]any), "@type")
return json.MarshalIndent(stuff, "", " ")
}
func handleResponseError(err error, w http.ResponseWriter, h *resourceHandler) {
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)
case codes.NotFound:
w.WriteHeader(http.StatusNotFound)
h.logger.Info("Failed to write to GRPC resource: Not found", "error", err)
case codes.PermissionDenied:
w.WriteHeader(http.StatusForbidden)
h.logger.Info("Failed to write to GRPC resource: User not authenticated", "error", err)
case codes.Aborted:
w.WriteHeader(http.StatusConflict)
h.logger.Info("Failed to write to GRPC resource: the request conflict with the current state of the target resource", "error", err)
default:
w.WriteHeader(http.StatusInternalServerError)
h.logger.Error("Failed to write to GRPC resource", "error", err)
}
} else {
w.WriteHeader(http.StatusInternalServerError)
h.logger.Error("Failed to write to GRPC resource: not able to parse error returned", "error", err)
}
w.Write([]byte(err.Error()))
}