mirror of https://github.com/hashicorp/consul
parent
82f1eacb14
commit
78ad8203a4
|
@ -6,7 +6,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/consul/acl"
|
"github.com/hashicorp/consul/acl"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
|
@ -273,53 +272,6 @@ func (s *HTTPServer) ACLPolicyCreate(resp http.ResponseWriter, req *http.Request
|
||||||
return s.aclPolicyWriteInternal(resp, req, "", true)
|
return s.aclPolicyWriteInternal(resp, req, "", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixTimeAndHashFields is used to help in decoding the ExpirationTTL, ExpirationTime, CreateTime, and Hash
|
|
||||||
// attributes from the ACL Token/Policy create/update requests. It is needed
|
|
||||||
// to help mapstructure decode things properly when decodeBody is used.
|
|
||||||
func fixTimeAndHashFields(raw interface{}) error {
|
|
||||||
rawMap, ok := raw.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if val, ok := rawMap["ExpirationTTL"]; ok {
|
|
||||||
if sval, ok := val.(string); ok {
|
|
||||||
d, err := time.ParseDuration(sval)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rawMap["ExpirationTTL"] = d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if val, ok := rawMap["ExpirationTime"]; ok {
|
|
||||||
if sval, ok := val.(string); ok {
|
|
||||||
t, err := time.Parse(time.RFC3339, sval)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rawMap["ExpirationTime"] = t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if val, ok := rawMap["CreateTime"]; ok {
|
|
||||||
if sval, ok := val.(string); ok {
|
|
||||||
t, err := time.Parse(time.RFC3339, sval)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rawMap["CreateTime"] = t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if val, ok := rawMap["Hash"]; ok {
|
|
||||||
if sval, ok := val.(string); ok {
|
|
||||||
rawMap["Hash"] = []byte(sval)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *HTTPServer) ACLPolicyWrite(resp http.ResponseWriter, req *http.Request, policyID string) (interface{}, error) {
|
func (s *HTTPServer) ACLPolicyWrite(resp http.ResponseWriter, req *http.Request, policyID string) (interface{}, error) {
|
||||||
return s.aclPolicyWriteInternal(resp, req, policyID, false)
|
return s.aclPolicyWriteInternal(resp, req, policyID, false)
|
||||||
}
|
}
|
||||||
|
@ -331,7 +283,7 @@ func (s *HTTPServer) aclPolicyWriteInternal(resp http.ResponseWriter, req *http.
|
||||||
s.parseToken(req, &args.Token)
|
s.parseToken(req, &args.Token)
|
||||||
s.parseEntMeta(req, &args.Policy.EnterpriseMeta)
|
s.parseEntMeta(req, &args.Policy.EnterpriseMeta)
|
||||||
|
|
||||||
if err := decodeBody(req, &args.Policy, fixTimeAndHashFields); err != nil {
|
if err := decodeBody(req.Body, &args.Policy); err != nil {
|
||||||
return nil, BadRequestError{Reason: fmt.Sprintf("Policy decoding failed: %v", err)}
|
return nil, BadRequestError{Reason: fmt.Sprintf("Policy decoding failed: %v", err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -521,7 +473,7 @@ func (s *HTTPServer) aclTokenSetInternal(resp http.ResponseWriter, req *http.Req
|
||||||
s.parseToken(req, &args.Token)
|
s.parseToken(req, &args.Token)
|
||||||
s.parseEntMeta(req, &args.ACLToken.EnterpriseMeta)
|
s.parseEntMeta(req, &args.ACLToken.EnterpriseMeta)
|
||||||
|
|
||||||
if err := decodeBody(req, &args.ACLToken, fixTimeAndHashFields); err != nil {
|
if err := decodeBody(req.Body, &args.ACLToken); err != nil {
|
||||||
return nil, BadRequestError{Reason: fmt.Sprintf("Token decoding failed: %v", err)}
|
return nil, BadRequestError{Reason: fmt.Sprintf("Token decoding failed: %v", err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -567,8 +519,7 @@ func (s *HTTPServer) ACLTokenClone(resp http.ResponseWriter, req *http.Request,
|
||||||
}
|
}
|
||||||
|
|
||||||
s.parseEntMeta(req, &args.ACLToken.EnterpriseMeta)
|
s.parseEntMeta(req, &args.ACLToken.EnterpriseMeta)
|
||||||
|
if err := decodeBody(req.Body, &args.ACLToken); err != nil {
|
||||||
if err := decodeBody(req, &args.ACLToken, fixTimeAndHashFields); err != nil && err.Error() != "EOF" {
|
|
||||||
return nil, BadRequestError{Reason: fmt.Sprintf("Token decoding failed: %v", err)}
|
return nil, BadRequestError{Reason: fmt.Sprintf("Token decoding failed: %v", err)}
|
||||||
}
|
}
|
||||||
s.parseToken(req, &args.Token)
|
s.parseToken(req, &args.Token)
|
||||||
|
@ -705,7 +656,7 @@ func (s *HTTPServer) ACLRoleWrite(resp http.ResponseWriter, req *http.Request, r
|
||||||
s.parseToken(req, &args.Token)
|
s.parseToken(req, &args.Token)
|
||||||
s.parseEntMeta(req, &args.Role.EnterpriseMeta)
|
s.parseEntMeta(req, &args.Role.EnterpriseMeta)
|
||||||
|
|
||||||
if err := decodeBody(req, &args.Role, fixTimeAndHashFields); err != nil {
|
if err := decodeBody(req.Body, &args.Role); err != nil {
|
||||||
return nil, BadRequestError{Reason: fmt.Sprintf("Role decoding failed: %v", err)}
|
return nil, BadRequestError{Reason: fmt.Sprintf("Role decoding failed: %v", err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -844,7 +795,7 @@ func (s *HTTPServer) ACLBindingRuleWrite(resp http.ResponseWriter, req *http.Req
|
||||||
s.parseToken(req, &args.Token)
|
s.parseToken(req, &args.Token)
|
||||||
s.parseEntMeta(req, &args.BindingRule.EnterpriseMeta)
|
s.parseEntMeta(req, &args.BindingRule.EnterpriseMeta)
|
||||||
|
|
||||||
if err := decodeBody(req, &args.BindingRule, fixTimeAndHashFields); err != nil {
|
if err := decodeBody(req.Body, &args.BindingRule); err != nil {
|
||||||
return nil, BadRequestError{Reason: fmt.Sprintf("BindingRule decoding failed: %v", err)}
|
return nil, BadRequestError{Reason: fmt.Sprintf("BindingRule decoding failed: %v", err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -980,7 +931,7 @@ func (s *HTTPServer) ACLAuthMethodWrite(resp http.ResponseWriter, req *http.Requ
|
||||||
s.parseToken(req, &args.Token)
|
s.parseToken(req, &args.Token)
|
||||||
s.parseEntMeta(req, &args.AuthMethod.EnterpriseMeta)
|
s.parseEntMeta(req, &args.AuthMethod.EnterpriseMeta)
|
||||||
|
|
||||||
if err := decodeBody(req, &args.AuthMethod, fixTimeAndHashFields); err != nil {
|
if err := decodeBody(req.Body, &args.AuthMethod); err != nil {
|
||||||
return nil, BadRequestError{Reason: fmt.Sprintf("AuthMethod decoding failed: %v", err)}
|
return nil, BadRequestError{Reason: fmt.Sprintf("AuthMethod decoding failed: %v", err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1029,7 +980,7 @@ func (s *HTTPServer) ACLLogin(resp http.ResponseWriter, req *http.Request) (inte
|
||||||
s.parseDC(req, &args.Datacenter)
|
s.parseDC(req, &args.Datacenter)
|
||||||
s.parseEntMeta(req, &args.Auth.EnterpriseMeta)
|
s.parseEntMeta(req, &args.Auth.EnterpriseMeta)
|
||||||
|
|
||||||
if err := decodeBody(req, &args.Auth, nil); err != nil {
|
if err := decodeBody(req.Body, &args.Auth); err != nil {
|
||||||
return nil, BadRequestError{Reason: fmt.Sprintf("Failed to decode request body:: %v", err)}
|
return nil, BadRequestError{Reason: fmt.Sprintf("Failed to decode request body:: %v", err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ func (s *HTTPServer) aclSet(resp http.ResponseWriter, req *http.Request, update
|
||||||
|
|
||||||
// Handle optional request body
|
// Handle optional request body
|
||||||
if req.ContentLength > 0 {
|
if req.ContentLength > 0 {
|
||||||
if err := decodeBody(req, &args.ACL, nil); err != nil {
|
if err := decodeBody(req.Body, &args.ACL); err != nil {
|
||||||
resp.WriteHeader(http.StatusBadRequest)
|
resp.WriteHeader(http.StatusBadRequest)
|
||||||
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
@ -457,11 +457,8 @@ func (s *HTTPServer) syncChanges() {
|
||||||
|
|
||||||
func (s *HTTPServer) AgentRegisterCheck(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPServer) AgentRegisterCheck(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
var args structs.CheckDefinition
|
var args structs.CheckDefinition
|
||||||
// Fixup the type decode of TTL or Interval.
|
|
||||||
decodeCB := func(raw interface{}) error {
|
if err := decodeBody(req.Body, &args); err != nil {
|
||||||
return FixupCheckType(raw)
|
|
||||||
}
|
|
||||||
if err := decodeBody(req, &args, decodeCB); err != nil {
|
|
||||||
resp.WriteHeader(http.StatusBadRequest)
|
resp.WriteHeader(http.StatusBadRequest)
|
||||||
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -606,7 +603,7 @@ type checkUpdate struct {
|
||||||
// APIs.
|
// APIs.
|
||||||
func (s *HTTPServer) AgentCheckUpdate(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPServer) AgentCheckUpdate(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
var update checkUpdate
|
var update checkUpdate
|
||||||
if err := decodeBody(req, &update, nil); err != nil {
|
if err := decodeBody(req.Body, &update); err != nil {
|
||||||
resp.WriteHeader(http.StatusBadRequest)
|
resp.WriteHeader(http.StatusBadRequest)
|
||||||
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -758,7 +755,7 @@ func (s *HTTPServer) AgentRegisterService(resp http.ResponseWriter, req *http.Re
|
||||||
var args structs.ServiceDefinition
|
var args structs.ServiceDefinition
|
||||||
// Fixup the type decode of TTL or Interval if a check if provided.
|
// Fixup the type decode of TTL or Interval if a check if provided.
|
||||||
|
|
||||||
if err := decodeBody(req, &args, registerServiceDecodeCB); err != nil {
|
if err := decodeBody(req.Body, &args); err != nil {
|
||||||
resp.WriteHeader(http.StatusBadRequest)
|
resp.WriteHeader(http.StatusBadRequest)
|
||||||
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -894,76 +891,6 @@ func (s *HTTPServer) AgentRegisterService(resp http.ResponseWriter, req *http.Re
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// registerServiceDecodeCB is used in AgentRegisterService for request body decoding
|
|
||||||
func registerServiceDecodeCB(raw interface{}) error {
|
|
||||||
rawMap, ok := raw.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// see https://github.com/hashicorp/consul/pull/3557 why we need this
|
|
||||||
// and why we should get rid of it.
|
|
||||||
lib.TranslateKeys(rawMap, map[string]string{
|
|
||||||
"enable_tag_override": "EnableTagOverride",
|
|
||||||
// Proxy Upstreams
|
|
||||||
"destination_name": "DestinationName", // string
|
|
||||||
"destination_type": "DestinationType", // string
|
|
||||||
"destination_namespace": "DestinationNamespace", // string
|
|
||||||
"local_bind_port": "LocalBindPort", // int
|
|
||||||
"local_bind_address": "LocalBindAddress", // string
|
|
||||||
// Proxy Config
|
|
||||||
"destination_service_name": "DestinationServiceName", // string (Proxy.)
|
|
||||||
"destination_service_id": "DestinationServiceID", // string
|
|
||||||
"local_service_port": "LocalServicePort", // int
|
|
||||||
"local_service_address": "LocalServiceAddress", // string
|
|
||||||
// SidecarService
|
|
||||||
"sidecar_service": "SidecarService", // ServiceDefinition (Connect.)
|
|
||||||
// Expose Config
|
|
||||||
"local_path_port": "LocalPathPort", // int (Proxy.Expose.Paths.)
|
|
||||||
"listener_port": "ListenerPort", // int
|
|
||||||
|
|
||||||
// DON'T Recurse into these opaque config maps or we might mangle user's
|
|
||||||
// keys. Note empty canonical is a special sentinel to prevent recursion.
|
|
||||||
"Meta": "",
|
|
||||||
|
|
||||||
"tagged_addresses": "TaggedAddresses", // map[string]structs.ServiceAddress{Address string; Port int}
|
|
||||||
|
|
||||||
// upstreams is an array but this prevents recursion into config field of
|
|
||||||
// any item in the array.
|
|
||||||
"Proxy.Config": "",
|
|
||||||
"Proxy.Upstreams.Config": "",
|
|
||||||
"Connect.Proxy.Config": "",
|
|
||||||
"Connect.Proxy.Upstreams.Config": "",
|
|
||||||
|
|
||||||
// Same exceptions as above, but for a nested sidecar_service note we use
|
|
||||||
// the canonical form SidecarService since that is translated by the time
|
|
||||||
// the lookup here happens.
|
|
||||||
"Connect.SidecarService.Meta": "",
|
|
||||||
"Connect.SidecarService.Proxy.Config": "",
|
|
||||||
"Connect.SidecarService.Proxy.Upstreams.config": "",
|
|
||||||
})
|
|
||||||
|
|
||||||
for k, v := range rawMap {
|
|
||||||
switch strings.ToLower(k) {
|
|
||||||
case "check":
|
|
||||||
if err := FixupCheckType(v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "checks":
|
|
||||||
chkTypes, ok := v.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, chkType := range chkTypes {
|
|
||||||
if err := FixupCheckType(chkType); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *HTTPServer) AgentDeregisterService(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPServer) AgentDeregisterService(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
serviceID := strings.TrimPrefix(req.URL.Path, "/v1/agent/service/deregister/")
|
serviceID := strings.TrimPrefix(req.URL.Path, "/v1/agent/service/deregister/")
|
||||||
|
|
||||||
|
@ -1180,7 +1107,7 @@ func (s *HTTPServer) AgentToken(resp http.ResponseWriter, req *http.Request) (in
|
||||||
// The body is just the token, but it's in a JSON object so we can add
|
// The body is just the token, but it's in a JSON object so we can add
|
||||||
// fields to this later if needed.
|
// fields to this later if needed.
|
||||||
var args api.AgentToken
|
var args api.AgentToken
|
||||||
if err := decodeBody(req, &args, nil); err != nil {
|
if err := decodeBody(req.Body, &args); err != nil {
|
||||||
resp.WriteHeader(http.StatusBadRequest)
|
resp.WriteHeader(http.StatusBadRequest)
|
||||||
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -1339,7 +1266,7 @@ func (s *HTTPServer) AgentConnectAuthorize(resp http.ResponseWriter, req *http.R
|
||||||
|
|
||||||
// Decode the request from the request body
|
// Decode the request from the request body
|
||||||
var authReq structs.ConnectAuthorizeRequest
|
var authReq structs.ConnectAuthorizeRequest
|
||||||
if err := decodeBody(req, &authReq, nil); err != nil {
|
if err := decodeBody(req.Body, &authReq); err != nil {
|
||||||
return nil, BadRequestError{fmt.Sprintf("Request decode failed: %v", err)}
|
return nil, BadRequestError{fmt.Sprintf("Request decode failed: %v", err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,14 +10,12 @@ import (
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
)
|
)
|
||||||
|
|
||||||
var durations = NewDurationFixer("interval", "timeout", "deregistercriticalserviceafter")
|
|
||||||
|
|
||||||
func (s *HTTPServer) CatalogRegister(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPServer) CatalogRegister(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
metrics.IncrCounterWithLabels([]string{"client", "api", "catalog_register"}, 1,
|
metrics.IncrCounterWithLabels([]string{"client", "api", "catalog_register"}, 1,
|
||||||
[]metrics.Label{{Name: "node", Value: s.nodeName()}})
|
[]metrics.Label{{Name: "node", Value: s.nodeName()}})
|
||||||
|
|
||||||
var args structs.RegisterRequest
|
var args structs.RegisterRequest
|
||||||
if err := decodeBody(req, &args, durations.FixupDurations); err != nil {
|
if err := decodeBody(req.Body, &args); err != nil {
|
||||||
resp.WriteHeader(http.StatusBadRequest)
|
resp.WriteHeader(http.StatusBadRequest)
|
||||||
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -46,7 +44,7 @@ func (s *HTTPServer) CatalogDeregister(resp http.ResponseWriter, req *http.Reque
|
||||||
[]metrics.Label{{Name: "node", Value: s.nodeName()}})
|
[]metrics.Label{{Name: "node", Value: s.nodeName()}})
|
||||||
|
|
||||||
var args structs.DeregisterRequest
|
var args structs.DeregisterRequest
|
||||||
if err := decodeBody(req, &args, nil); err != nil {
|
if err := decodeBody(req.Body, &args); err != nil {
|
||||||
resp.WriteHeader(http.StatusBadRequest)
|
resp.WriteHeader(http.StatusBadRequest)
|
||||||
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
@ -103,7 +103,7 @@ func (s *HTTPServer) ConfigApply(resp http.ResponseWriter, req *http.Request) (i
|
||||||
s.parseToken(req, &args.Token)
|
s.parseToken(req, &args.Token)
|
||||||
|
|
||||||
var raw map[string]interface{}
|
var raw map[string]interface{}
|
||||||
if err := decodeBody(req, &raw, nil); err != nil {
|
if err := decodeBodyDeprecated(req, &raw, nil); err != nil {
|
||||||
return nil, BadRequestError{Reason: fmt.Sprintf("Request decoding failed: %v", err)}
|
return nil, BadRequestError{Reason: fmt.Sprintf("Request decoding failed: %v", err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ func (s *HTTPServer) ConnectCAConfigurationSet(resp http.ResponseWriter, req *ht
|
||||||
var args structs.CARequest
|
var args structs.CARequest
|
||||||
s.parseDC(req, &args.Datacenter)
|
s.parseDC(req, &args.Datacenter)
|
||||||
s.parseToken(req, &args.Token)
|
s.parseToken(req, &args.Token)
|
||||||
if err := decodeBody(req, &args.Config, nil); err != nil {
|
if err := decodeBody(req.Body, &args.Config); err != nil {
|
||||||
resp.WriteHeader(http.StatusBadRequest)
|
resp.WriteHeader(http.StatusBadRequest)
|
||||||
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
@ -150,7 +150,7 @@ func (s *HTTPServer) CoordinateUpdate(resp http.ResponseWriter, req *http.Reques
|
||||||
}
|
}
|
||||||
|
|
||||||
args := structs.CoordinateUpdateRequest{}
|
args := structs.CoordinateUpdateRequest{}
|
||||||
if err := decodeBody(req, &args, nil); err != nil {
|
if err := decodeBody(req.Body, &args); err != nil {
|
||||||
resp.WriteHeader(http.StatusBadRequest)
|
resp.WriteHeader(http.StatusBadRequest)
|
||||||
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package agent
|
package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -28,7 +29,7 @@ func (s *HTTPServer) DiscoveryChainRead(resp http.ResponseWriter, req *http.Requ
|
||||||
|
|
||||||
if req.Method == "POST" {
|
if req.Method == "POST" {
|
||||||
var raw map[string]interface{}
|
var raw map[string]interface{}
|
||||||
if err := decodeBody(req, &raw, nil); err != nil {
|
if err := decodeBody(req.Body, &raw); err != nil {
|
||||||
return nil, BadRequestError{Reason: fmt.Sprintf("Request decoding failed: %v", err)}
|
return nil, BadRequestError{Reason: fmt.Sprintf("Request decoding failed: %v", err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,6 +92,63 @@ type discoveryChainReadRequest struct {
|
||||||
OverrideConnectTimeout time.Duration
|
OverrideConnectTimeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *discoveryChainReadRequest) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
type Alias discoveryChainReadRequest
|
||||||
|
aux := &struct {
|
||||||
|
OverrideConnectTimeout interface{}
|
||||||
|
OverrideProtocol interface{}
|
||||||
|
OverrideMeshGateway *struct{ Mode interface{} }
|
||||||
|
|
||||||
|
OverrideConnectTimeoutSnake interface{} `json:"override_connect_timeout"`
|
||||||
|
OverrideProtocolSnake interface{} `json:"override_protocol"`
|
||||||
|
OverrideMeshGatewaySnake *struct{ Mode interface{} } `json:"override_mesh_gateway"`
|
||||||
|
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(t),
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(data, &aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if aux.OverrideConnectTimeout == nil {
|
||||||
|
aux.OverrideConnectTimeout = aux.OverrideConnectTimeoutSnake
|
||||||
|
}
|
||||||
|
if aux.OverrideProtocol == nil {
|
||||||
|
aux.OverrideProtocol = aux.OverrideProtocolSnake
|
||||||
|
}
|
||||||
|
if aux.OverrideMeshGateway == nil {
|
||||||
|
aux.OverrideMeshGateway = aux.OverrideMeshGatewaySnake
|
||||||
|
}
|
||||||
|
|
||||||
|
// weakly typed input
|
||||||
|
if aux.OverrideProtocol != nil {
|
||||||
|
switch v := aux.OverrideProtocol.(type) {
|
||||||
|
case string, float64, bool:
|
||||||
|
t.OverrideProtocol = fmt.Sprintf("%v", v)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("OverrideProtocol: invalid type %T", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if aux.OverrideMeshGateway != nil {
|
||||||
|
t.OverrideMeshGateway.Mode = structs.MeshGatewayMode(fmt.Sprintf("%v", aux.OverrideMeshGateway.Mode))
|
||||||
|
}
|
||||||
|
|
||||||
|
// duration
|
||||||
|
if aux.OverrideConnectTimeout != nil {
|
||||||
|
switch v := aux.OverrideConnectTimeout.(type) {
|
||||||
|
case string:
|
||||||
|
if t.OverrideConnectTimeout, err = time.ParseDuration(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
t.OverrideConnectTimeout = time.Duration(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// discoveryChainReadResponse is the API variation of structs.DiscoveryChainResponse
|
// discoveryChainReadResponse is the API variation of structs.DiscoveryChainResponse
|
||||||
type discoveryChainReadResponse struct {
|
type discoveryChainReadResponse struct {
|
||||||
Chain *structs.CompiledDiscoveryChain
|
Chain *structs.CompiledDiscoveryChain
|
||||||
|
|
|
@ -572,8 +572,17 @@ func (s *HTTPServer) Index(resp http.ResponseWriter, req *http.Request) {
|
||||||
http.Redirect(resp, req, s.agent.config.UIContentPath, http.StatusMovedPermanently) // 301
|
http.Redirect(resp, req, s.agent.config.UIContentPath, http.StatusMovedPermanently) // 301
|
||||||
}
|
}
|
||||||
|
|
||||||
// decodeBody is used to decode a JSON request body
|
func decodeBody(body io.Reader, out interface{}) error {
|
||||||
func decodeBody(req *http.Request, out interface{}, cb func(interface{}) error) error {
|
if body == nil {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.NewDecoder(body).Decode(&out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeBodyDeprecated is deprecated, please ues decodeBody above.
|
||||||
|
// decodeBodyDeprecated is used to decode a JSON request body
|
||||||
|
func decodeBodyDeprecated(req *http.Request, out interface{}, cb func(interface{}) error) error {
|
||||||
// This generally only happens in tests since real HTTP requests set
|
// This generally only happens in tests since real HTTP requests set
|
||||||
// a non-nil body with no content. We guard against it anyways to prevent
|
// a non-nil body with no content. We guard against it anyways to prevent
|
||||||
// a panic. The EOF response is the same behavior as an empty reader.
|
// a panic. The EOF response is the same behavior as an empty reader.
|
||||||
|
|
|
@ -33,8 +33,7 @@ package agent
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -348,13 +347,13 @@ var translateScriptArgsTCs = []translateKeyTestCase{
|
||||||
jsonFmtStr: "{" + scriptFields[0] + "," + scriptFields[2] + "}",
|
jsonFmtStr: "{" + scriptFields[0] + "," + scriptFields[2] + "}",
|
||||||
equalityFn: scriptArgsEqFn,
|
equalityFn: scriptArgsEqFn,
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// desc: "scriptArgs: second and third set",
|
desc: "scriptArgs: second and third set",
|
||||||
// in: []interface{}{`["2"]`, `["3"]`},
|
in: []interface{}{`["2"]`, `["3"]`},
|
||||||
// want: []string{"2"},
|
want: []string{"2"},
|
||||||
// jsonFmtStr: "{" + scriptFields[1] + "," + scriptFields[2] + "}",
|
jsonFmtStr: "{" + scriptFields[1] + "," + scriptFields[2] + "}",
|
||||||
// equalityFn: scriptArgsEqFn,
|
equalityFn: scriptArgsEqFn,
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
desc: "scriptArgs: first set",
|
desc: "scriptArgs: first set",
|
||||||
in: []interface{}{`["1"]`},
|
in: []interface{}{`["1"]`},
|
||||||
|
@ -618,15 +617,6 @@ var translateServiceIDTCs = []translateKeyTestCase{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/acl_endpoint.go:
|
|
||||||
// 327 s.parseToken(req, &args.Token)
|
|
||||||
// 328
|
|
||||||
// 329: if err := decodeBody(req, &args.Policy, fixTimeAndHashFields); err != nil {
|
|
||||||
// 330 return nil, BadRequestError{Reason: fmt.Sprintf("Policy decoding failed: %v", err)}
|
|
||||||
// 331 }
|
|
||||||
// ==================================
|
|
||||||
|
|
||||||
// ACLPolicySetRequest:
|
// ACLPolicySetRequest:
|
||||||
// Policy structs.ACLPolicy
|
// Policy structs.ACLPolicy
|
||||||
// ID string
|
// ID string
|
||||||
|
@ -646,12 +636,15 @@ func TestDecodeACLPolicyWrite(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range hashTestCases {
|
for _, tc := range hashTestCases {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
jsonStr := fmt.Sprintf(`{"Hash": %s}`, tc.hashes.in)
|
|
||||||
|
jsonStr := fmt.Sprintf(`{
|
||||||
|
"Hash": %s
|
||||||
|
}`, tc.hashes.in)
|
||||||
body := bytes.NewBuffer([]byte(jsonStr))
|
body := bytes.NewBuffer([]byte(jsonStr))
|
||||||
req := httptest.NewRequest("POST", "http://foo.com", body)
|
|
||||||
|
|
||||||
var out structs.ACLPolicy
|
var out structs.ACLPolicy
|
||||||
err := decodeBody(req, &out, fixTimeAndHashFields)
|
err := decodeBody(body, &out)
|
||||||
|
|
||||||
if err != nil && !tc.wantErr {
|
if err != nil && !tc.wantErr {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -666,15 +659,6 @@ func TestDecodeACLPolicyWrite(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/acl_endpoint.go:
|
|
||||||
// 511 s.parseToken(req, &args.Token)
|
|
||||||
// 512
|
|
||||||
// 513: if err := decodeBody(req, &args.ACLToken, fixTimeAndHashFields); err != nil {
|
|
||||||
// 514 return nil, BadRequestError{Reason: fmt.Sprintf("Token decoding failed: %v", err)}
|
|
||||||
// 515 }
|
|
||||||
// ==================================
|
|
||||||
|
|
||||||
// ACLTokenSetRequest:
|
// ACLTokenSetRequest:
|
||||||
// ACLToken structs.ACLToken
|
// ACLToken structs.ACLToken
|
||||||
// AccessorID string
|
// AccessorID string
|
||||||
|
@ -704,9 +688,7 @@ func TestDecodeACLPolicyWrite(t *testing.T) {
|
||||||
// Datacenter string
|
// Datacenter string
|
||||||
// WriteRequest structs.WriteRequest
|
// WriteRequest structs.WriteRequest
|
||||||
// Token string
|
// Token string
|
||||||
|
|
||||||
func TestDecodeACLToken(t *testing.T) {
|
func TestDecodeACLToken(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range translateValueTestCases {
|
for _, tc := range translateValueTestCases {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
// set up request body
|
// set up request body
|
||||||
|
@ -728,13 +710,12 @@ func TestDecodeACLToken(t *testing.T) {
|
||||||
"Hash": %s
|
"Hash": %s
|
||||||
}`, expTime, expTTL, createTime, hash))
|
}`, expTime, expTTL, createTime, hash))
|
||||||
|
|
||||||
// set up request
|
|
||||||
body := bytes.NewBuffer(bodyBytes)
|
body := bytes.NewBuffer(bodyBytes)
|
||||||
req := httptest.NewRequest("POST", "http://foo.com", body)
|
|
||||||
|
|
||||||
// decode body
|
// decode body
|
||||||
var out structs.ACLToken
|
var out structs.ACLToken
|
||||||
err := decodeBody(req, &out, fixTimeAndHashFields)
|
|
||||||
|
err := decodeBody(body, &out)
|
||||||
if err != nil && !tc.wantErr {
|
if err != nil && !tc.wantErr {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -775,27 +756,6 @@ func TestDecodeACLToken(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/acl_endpoint.go:
|
|
||||||
// 555 }
|
|
||||||
// 556
|
|
||||||
// 557: if err := decodeBody(req, &args.ACLToken, fixTimeAndHashFields); err != nil && err.Error() != "EOF" {
|
|
||||||
// 558 return nil, BadRequestError{Reason: fmt.Sprintf("Token decoding failed: %v", err)}
|
|
||||||
// 559 }
|
|
||||||
// ==================================
|
|
||||||
func TestDecodeACLTokenClone(t *testing.T) {
|
|
||||||
t.Skip("COVERED BY ABOVE (same structs.ACLTokenSetRequest).")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/acl_endpoint.go:
|
|
||||||
// 689 s.parseToken(req, &args.Token)
|
|
||||||
// 690
|
|
||||||
// 691: if err := decodeBody(req, &args.Role, fixTimeAndHashFields); err != nil {
|
|
||||||
// 692 return nil, BadRequestError{Reason: fmt.Sprintf("Role decoding failed: %v", err)}
|
|
||||||
// 693 }
|
|
||||||
// ==================================
|
|
||||||
|
|
||||||
// ACLRoleSetRequest:
|
// ACLRoleSetRequest:
|
||||||
// Role structs.ACLRole
|
// Role structs.ACLRole
|
||||||
// ID string
|
// ID string
|
||||||
|
@ -816,15 +776,17 @@ func TestDecodeACLTokenClone(t *testing.T) {
|
||||||
// Token string
|
// Token string
|
||||||
|
|
||||||
func TestDecodeACLRoleWrite(t *testing.T) {
|
func TestDecodeACLRoleWrite(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range hashTestCases {
|
for _, tc := range hashTestCases {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
jsonStr := fmt.Sprintf(`{"Hash": %s}`, tc.hashes.in)
|
|
||||||
|
jsonStr := fmt.Sprintf(`{
|
||||||
|
"Hash": %s
|
||||||
|
}`, tc.hashes.in)
|
||||||
body := bytes.NewBuffer([]byte(jsonStr))
|
body := bytes.NewBuffer([]byte(jsonStr))
|
||||||
req := httptest.NewRequest("POST", "http://foo.com", body)
|
|
||||||
|
|
||||||
var out structs.ACLRole
|
var out structs.ACLRole
|
||||||
err := decodeBody(req, &out, fixTimeAndHashFields)
|
err := decodeBody(body, &out)
|
||||||
|
|
||||||
if err == nil && tc.wantErr {
|
if err == nil && tc.wantErr {
|
||||||
t.Fatal("expected error, got nil")
|
t.Fatal("expected error, got nil")
|
||||||
}
|
}
|
||||||
|
@ -839,111 +801,6 @@ func TestDecodeACLRoleWrite(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/acl_endpoint.go:
|
|
||||||
// 822 s.parseToken(req, &args.Token)
|
|
||||||
// 823
|
|
||||||
// 824: if err := decodeBody(req, &args.BindingRule, fixTimeAndHashFields); err != nil {
|
|
||||||
// 825 return nil, BadRequestError{Reason: fmt.Sprintf("BindingRule decoding failed: %v", err)}
|
|
||||||
// 826 }
|
|
||||||
// ==================================
|
|
||||||
//
|
|
||||||
// ACLBindingRuleSetRequest:
|
|
||||||
// BindingRule structs.ACLBindingRule
|
|
||||||
// ID string
|
|
||||||
// Description string
|
|
||||||
// AuthMethod string
|
|
||||||
// Selector string
|
|
||||||
// BindType string
|
|
||||||
// BindName string
|
|
||||||
// RaftIndex structs.RaftIndex
|
|
||||||
// CreateIndex uint64
|
|
||||||
// ModifyIndex uint64
|
|
||||||
// Datacenter string
|
|
||||||
// WriteRequest structs.WriteRequest
|
|
||||||
// Token string
|
|
||||||
func TestDecodeACLBindingRuleWrite(t *testing.T) {
|
|
||||||
t.Skip("DONE. no special fields to parse; fixTimeAndHashFields: no time or hash fields.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/acl_endpoint.go:
|
|
||||||
// 954 s.parseToken(req, &args.Token)
|
|
||||||
// 955
|
|
||||||
// 956: if err := decodeBody(req, &args.AuthMethod, fixTimeAndHashFields); err != nil {
|
|
||||||
// 957 return nil, BadRequestError{Reason: fmt.Sprintf("AuthMethod decoding failed: %v", err)}
|
|
||||||
// 958 }
|
|
||||||
// ==================================
|
|
||||||
// ACLAuthMethodSetRequest:
|
|
||||||
// AuthMethod structs.ACLAuthMethod
|
|
||||||
// Name string
|
|
||||||
// Type string
|
|
||||||
// Description string
|
|
||||||
// Config map[string]interface {}
|
|
||||||
// RaftIndex structs.RaftIndex
|
|
||||||
// CreateIndex uint64
|
|
||||||
// ModifyIndex uint64
|
|
||||||
// Datacenter string
|
|
||||||
// WriteRequest structs.WriteRequest
|
|
||||||
// Token string
|
|
||||||
func TestDecodeACLAuthMethodWrite(t *testing.T) {
|
|
||||||
t.Skip("DONE. no special fields to parse; fixTimeAndHashFields: no time or hash fields.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/acl_endpoint.go:
|
|
||||||
// 1000 s.parseDC(req, &args.Datacenter)
|
|
||||||
// 1001
|
|
||||||
// 1002: if err := decodeBody(req, &args.Auth, nil); err != nil {
|
|
||||||
// 1003 return nil, BadRequestError{Reason: fmt.Sprintf("Failed to decode request body:: %v", err)}
|
|
||||||
// 1004 }
|
|
||||||
// ==================================
|
|
||||||
// ACLLoginRequest:
|
|
||||||
// Auth *structs.ACLLoginParams
|
|
||||||
// AuthMethod string
|
|
||||||
// BearerToken string
|
|
||||||
// Meta map[string]string
|
|
||||||
// Datacenter string
|
|
||||||
// WriteRequest structs.WriteRequest
|
|
||||||
// Token string
|
|
||||||
func TestDecodeACLLogin(t *testing.T) {
|
|
||||||
t.Skip("DONE. no special fields to parse; no decodeBody callback used.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/acl_endpoint_legacy.go:
|
|
||||||
// 66 // Handle optional request body
|
|
||||||
// 67 if req.ContentLength > 0 {
|
|
||||||
// 68: if err := decodeBody(req, &args.ACL, nil); err != nil {
|
|
||||||
// 69 resp.WriteHeader(http.StatusBadRequest)
|
|
||||||
// 70 fmt.Fprintf(resp, "Request decode failed: %v", err)
|
|
||||||
// ==================================
|
|
||||||
//
|
|
||||||
// ACLRequest:
|
|
||||||
// Datacenter string
|
|
||||||
// Op structs.ACLOp
|
|
||||||
// ACL structs.ACL
|
|
||||||
// ID string
|
|
||||||
// Name string
|
|
||||||
// Type string
|
|
||||||
// Rules string
|
|
||||||
// RaftIndex structs.RaftIndex
|
|
||||||
// CreateIndex uint64
|
|
||||||
// ModifyIndex uint64
|
|
||||||
// WriteRequest structs.WriteRequest
|
|
||||||
// Token string
|
|
||||||
func TestDecodeACLUpdate(t *testing.T) {
|
|
||||||
t.Skip("DONE. no special fields to parse; no decodeBody callback used.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/agent_endpoint.go:
|
|
||||||
// 461 return FixupCheckType(raw)
|
|
||||||
// 462 }
|
|
||||||
// 463: if err := decodeBody(req, &args, decodeCB); err != nil {
|
|
||||||
// 464 resp.WriteHeader(http.StatusBadRequest)
|
|
||||||
// 465 fmt.Fprintf(resp, "Request decode failed: %v", err)
|
|
||||||
// ==================================
|
|
||||||
// CheckDefinition:
|
// CheckDefinition:
|
||||||
// ID types.CheckID
|
// ID types.CheckID
|
||||||
// Name string
|
// Name string
|
||||||
|
@ -976,16 +833,17 @@ func TestDecodeAgentRegisterCheck(t *testing.T) {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
// set up request body
|
// set up request body
|
||||||
jsonStr := fmt.Sprintf(`{
|
jsonStr := fmt.Sprintf(`{
|
||||||
|
|
||||||
"Interval": %[1]s,
|
"Interval": %[1]s,
|
||||||
"Timeout": %[1]s,
|
"Timeout": %[1]s,
|
||||||
"TTL": %[1]s,
|
"TTL": %[1]s,
|
||||||
"DeregisterCriticalServiceAfter": %[1]s
|
"DeregisterCriticalServiceAfter": %[1]s
|
||||||
}`, tc.durations.in)
|
}`, tc.durations.in)
|
||||||
body := bytes.NewBuffer([]byte(jsonStr))
|
body := bytes.NewBuffer([]byte(jsonStr))
|
||||||
req := httptest.NewRequest("POST", "http://foo.com", body)
|
|
||||||
|
|
||||||
var out structs.CheckDefinition
|
var out structs.CheckDefinition
|
||||||
err := decodeBody(req, &out, FixupCheckType)
|
err := decodeBody(body, &out)
|
||||||
|
|
||||||
if err == nil && tc.wantErr {
|
if err == nil && tc.wantErr {
|
||||||
t.Fatal("expected err, got nil")
|
t.Fatal("expected err, got nil")
|
||||||
}
|
}
|
||||||
|
@ -998,19 +856,17 @@ func TestDecodeAgentRegisterCheck(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// decodeCB:
|
|
||||||
// - Header field
|
|
||||||
// - translate keys
|
|
||||||
|
|
||||||
for _, tc := range checkTypeHeaderTestCases {
|
for _, tc := range checkTypeHeaderTestCases {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
// set up request body
|
// set up request body
|
||||||
jsonStr := fmt.Sprintf(`{"Header": %s}`, tc.in)
|
jsonStr := fmt.Sprintf(`{"Header": %s}`, tc.in)
|
||||||
|
|
||||||
body := bytes.NewBuffer([]byte(jsonStr))
|
body := bytes.NewBuffer([]byte(jsonStr))
|
||||||
req := httptest.NewRequest("POST", "http://foo.com", body)
|
|
||||||
|
|
||||||
var out structs.CheckDefinition
|
var out structs.CheckDefinition
|
||||||
err := decodeBody(req, &out, FixupCheckType)
|
err := decodeBody(body, &out)
|
||||||
|
|
||||||
if err == nil && tc.wantErr {
|
if err == nil && tc.wantErr {
|
||||||
t.Fatal("expected err, got nil")
|
t.Fatal("expected err, got nil")
|
||||||
}
|
}
|
||||||
|
@ -1027,11 +883,12 @@ func TestDecodeAgentRegisterCheck(t *testing.T) {
|
||||||
for _, tc := range tcs {
|
for _, tc := range tcs {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
jsonStr := fmt.Sprintf(tc.jsonFmtStr, tc.in...)
|
jsonStr := fmt.Sprintf(tc.jsonFmtStr, tc.in...)
|
||||||
|
|
||||||
body := bytes.NewBuffer([]byte(jsonStr))
|
body := bytes.NewBuffer([]byte(jsonStr))
|
||||||
req := httptest.NewRequest("POST", "http://foo.com", body)
|
|
||||||
|
|
||||||
var out structs.CheckDefinition
|
var out structs.CheckDefinition
|
||||||
err := decodeBody(req, &out, FixupCheckType)
|
err := decodeBody(body, &out)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -1045,42 +902,6 @@ func TestDecodeAgentRegisterCheck(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/agent_endpoint.go:
|
|
||||||
// 603 func (s *HTTPServer) AgentCheckUpdate(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
||||||
// 604 var update checkUpdate
|
|
||||||
// 605: if err := decodeBody(req, &update, nil); err != nil {
|
|
||||||
// 606 resp.WriteHeader(http.StatusBadRequest)
|
|
||||||
// 607 fmt.Fprintf(resp, "Request decode failed: %v", err)
|
|
||||||
// ==================================
|
|
||||||
// type checkUpdate struct {
|
|
||||||
// Status string
|
|
||||||
// Output string
|
|
||||||
// }
|
|
||||||
func TestDecodeAgentCheckUpdate(t *testing.T) {
|
|
||||||
t.Skip("DONE. no special fields to parse; no decodeBody callback used.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/agent_endpoint.go:
|
|
||||||
// 822 return nil
|
|
||||||
// 823 }
|
|
||||||
// 824: if err := decodeBody(req, &args, decodeCB); err != nil {
|
|
||||||
// 825 resp.WriteHeader(http.StatusBadRequest)
|
|
||||||
// 826 fmt.Fprintf(resp, "Request decode failed: %v", err)
|
|
||||||
// ==================================
|
|
||||||
//
|
|
||||||
// decodeCB:
|
|
||||||
// -----------
|
|
||||||
// 1. lib.TranslateKeys()
|
|
||||||
// 2. FixupCheckType
|
|
||||||
// a. lib.TranslateKeys()
|
|
||||||
// b. parseDuration()
|
|
||||||
// c. parseHeaderMap()
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Type fields:
|
|
||||||
// -----------
|
|
||||||
// ServiceDefinition:
|
// ServiceDefinition:
|
||||||
// Kind structs.ServiceKind
|
// Kind structs.ServiceKind
|
||||||
// ID string
|
// ID string
|
||||||
|
@ -1151,8 +972,6 @@ func TestDecodeAgentCheckUpdate(t *testing.T) {
|
||||||
// Native bool
|
// Native bool
|
||||||
// SidecarService *structs.ServiceDefinition
|
// SidecarService *structs.ServiceDefinition
|
||||||
func TestDecodeAgentRegisterService(t *testing.T) {
|
func TestDecodeAgentRegisterService(t *testing.T) {
|
||||||
var callback = registerServiceDecodeCB
|
|
||||||
|
|
||||||
// key translation tests:
|
// key translation tests:
|
||||||
// decodeCB fields:
|
// decodeCB fields:
|
||||||
// --------------------
|
// --------------------
|
||||||
|
@ -1325,14 +1144,15 @@ func TestDecodeAgentRegisterService(t *testing.T) {
|
||||||
desc: "DestinationNamespace: both set",
|
desc: "DestinationNamespace: both set",
|
||||||
in: []interface{}{`"a"`, `"b"`},
|
in: []interface{}{`"a"`, `"b"`},
|
||||||
want: "a",
|
want: "a",
|
||||||
jsonFmtStr: `{"Proxy": {"Upstreams": [{` + destinationNamespaceFields[1] + `}]}}`,
|
jsonFmtStr: `{"Proxy": {"Upstreams": [{` + strings.Join(destinationNamespaceFields, ",") + `}]}}`,
|
||||||
|
|
||||||
equalityFn: destinationNamespaceEqFn,
|
equalityFn: destinationNamespaceEqFn,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "DestinationNamespace: first set",
|
desc: "DestinationNamespace: first set",
|
||||||
in: []interface{}{`"a"`},
|
in: []interface{}{`"a"`},
|
||||||
want: "a",
|
want: "a",
|
||||||
jsonFmtStr: `{"Proxy": {"Upstreams": [{` + destinationNamespaceFields[1] + `}]}}`,
|
jsonFmtStr: `{"Proxy": {"Upstreams": [{` + destinationNamespaceFields[0] + `}]}}`,
|
||||||
equalityFn: destinationNamespaceEqFn,
|
equalityFn: destinationNamespaceEqFn,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1843,10 +1663,10 @@ func TestDecodeAgentRegisterService(t *testing.T) {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
checkJSONStr := fmt.Sprintf(tc.jsonFmtStr, tc.in...)
|
checkJSONStr := fmt.Sprintf(tc.jsonFmtStr, tc.in...)
|
||||||
body := bytes.NewBuffer([]byte(checkJSONStr))
|
body := bytes.NewBuffer([]byte(checkJSONStr))
|
||||||
req := httptest.NewRequest("POST", "http://foo.com", body)
|
|
||||||
|
|
||||||
var out structs.ServiceDefinition
|
var out structs.ServiceDefinition
|
||||||
err := decodeBody(req, &out, callback)
|
err := decodeBody(body, &out)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -1864,26 +1684,26 @@ func TestDecodeAgentRegisterService(t *testing.T) {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
// set up request body
|
// set up request body
|
||||||
jsonStr := fmt.Sprintf(`{
|
jsonStr := fmt.Sprintf(`{
|
||||||
"Check": {
|
"Check": {
|
||||||
"Interval": %[1]s,
|
|
||||||
"Timeout": %[1]s,
|
|
||||||
"TTL": %[1]s,
|
|
||||||
"DeregisterCriticalServiceAfter": %[1]s
|
|
||||||
},
|
|
||||||
"Checks": [
|
|
||||||
{
|
|
||||||
"Interval": %[1]s,
|
"Interval": %[1]s,
|
||||||
"Timeout": %[1]s,
|
"Timeout": %[1]s,
|
||||||
"TTL": %[1]s,
|
"TTL": %[1]s,
|
||||||
"DeregisterCriticalServiceAfter": %[1]s
|
"DeregisterCriticalServiceAfter": %[1]s
|
||||||
}
|
},
|
||||||
]
|
"Checks": [
|
||||||
}`, tc.durations.in)
|
{
|
||||||
|
"Interval": %[1]s,
|
||||||
|
"Timeout": %[1]s,
|
||||||
|
"TTL": %[1]s,
|
||||||
|
"DeregisterCriticalServiceAfter": %[1]s
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`, tc.durations.in)
|
||||||
body := bytes.NewBuffer([]byte(jsonStr))
|
body := bytes.NewBuffer([]byte(jsonStr))
|
||||||
req := httptest.NewRequest("POST", "http://foo.com", body)
|
|
||||||
|
|
||||||
var out structs.ServiceDefinition
|
var out structs.ServiceDefinition
|
||||||
err := decodeBody(req, &out, callback)
|
err := decodeBody(body, &out)
|
||||||
|
|
||||||
if err == nil && tc.wantErr {
|
if err == nil && tc.wantErr {
|
||||||
t.Fatal("expected err, got nil")
|
t.Fatal("expected err, got nil")
|
||||||
}
|
}
|
||||||
|
@ -1917,10 +1737,10 @@ func TestDecodeAgentRegisterService(t *testing.T) {
|
||||||
}`, checkJSONStr)
|
}`, checkJSONStr)
|
||||||
|
|
||||||
body := bytes.NewBuffer([]byte(jsonStr))
|
body := bytes.NewBuffer([]byte(jsonStr))
|
||||||
req := httptest.NewRequest("POST", "http://foo.com", body)
|
|
||||||
|
|
||||||
var out structs.ServiceDefinition
|
var out structs.ServiceDefinition
|
||||||
err := decodeBody(req, &out, callback)
|
err := decodeBody(body, &out)
|
||||||
|
|
||||||
if err == nil && tc.wantErr {
|
if err == nil && tc.wantErr {
|
||||||
t.Fatal("expected err, got nil")
|
t.Fatal("expected err, got nil")
|
||||||
}
|
}
|
||||||
|
@ -1951,10 +1771,10 @@ func TestDecodeAgentRegisterService(t *testing.T) {
|
||||||
"Checks": [%[1]s]
|
"Checks": [%[1]s]
|
||||||
}`, checkJSONStr)
|
}`, checkJSONStr)
|
||||||
body := bytes.NewBuffer([]byte(jsonStr))
|
body := bytes.NewBuffer([]byte(jsonStr))
|
||||||
req := httptest.NewRequest("POST", "http://foo.com", body)
|
|
||||||
|
|
||||||
var out structs.ServiceDefinition
|
var out structs.ServiceDefinition
|
||||||
err := decodeBody(req, &out, callback)
|
err := decodeBody(body, &out)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -1971,44 +1791,6 @@ func TestDecodeAgentRegisterService(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/agent_endpoint.go:
|
|
||||||
// 1173 // fields to this later if needed.
|
|
||||||
// 1174 var args api.AgentToken
|
|
||||||
// 1175: if err := decodeBody(req, &args, nil); err != nil {
|
|
||||||
// 1176 resp.WriteHeader(http.StatusBadRequest)
|
|
||||||
// 1177 fmt.Fprintf(resp, "Request decode failed: %v", err)
|
|
||||||
// ==================================
|
|
||||||
// AgentToken:
|
|
||||||
// Token string
|
|
||||||
func TestDecodeAgentToken(t *testing.T) {
|
|
||||||
t.Skip("DONE. no special fields to parse; no decodeBody callback used.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/agent_endpoint.go:
|
|
||||||
// 1332 // Decode the request from the request body
|
|
||||||
// 1333 var authReq structs.ConnectAuthorizeRequest
|
|
||||||
// 1334: if err := decodeBody(req, &authReq, nil); err != nil {
|
|
||||||
// 1335 return nil, BadRequestError{fmt.Sprintf("Request decode failed: %v", err)}
|
|
||||||
// 1336 }
|
|
||||||
// ==================================
|
|
||||||
// ConnectAuthorizeRequest:
|
|
||||||
// Target string
|
|
||||||
// ClientCertURI string
|
|
||||||
// ClientCertSerial string
|
|
||||||
func TestDecodeAgentConnectAuthorize(t *testing.T) {
|
|
||||||
t.Skip("DONE. no special fields to parse; no decodeBody callback used.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/catalog_endpoint.go:
|
|
||||||
// 18
|
|
||||||
// 19 var args structs.RegisterRequest
|
|
||||||
// 20: if err := decodeBody(req, &args, durations.FixupDurations); err != nil {
|
|
||||||
// 21 resp.WriteHeader(http.StatusBadRequest)
|
|
||||||
// 22 fmt.Fprintf(resp, "Request decode failed: %v", err)
|
|
||||||
// ==================================
|
|
||||||
// RegisterRequest:
|
// RegisterRequest:
|
||||||
// Datacenter string
|
// Datacenter string
|
||||||
// ID types.NodeID
|
// ID types.NodeID
|
||||||
|
@ -2161,16 +1943,19 @@ func TestDecodeCatalogRegister(t *testing.T) {
|
||||||
}
|
}
|
||||||
}`, tc.durations.in)
|
}`, tc.durations.in)
|
||||||
body := bytes.NewBuffer([]byte(jsonStr))
|
body := bytes.NewBuffer([]byte(jsonStr))
|
||||||
req := httptest.NewRequest("POST", "http://foo.com", body)
|
|
||||||
|
|
||||||
var out structs.RegisterRequest
|
var out structs.RegisterRequest
|
||||||
err := decodeBody(req, &out, durations.FixupDurations)
|
err := decodeBody(body, &out)
|
||||||
|
|
||||||
if err == nil && tc.wantErr {
|
if err == nil && tc.wantErr {
|
||||||
t.Fatal("expected err, got nil")
|
t.Fatal("expected err, got nil")
|
||||||
}
|
}
|
||||||
if err != nil && !tc.wantErr {
|
if err != nil && !tc.wantErr {
|
||||||
t.Fatalf("expected nil error, got %v", err)
|
t.Fatalf("expected nil error, got %v", err)
|
||||||
}
|
}
|
||||||
|
if err != nil && tc.wantErr {
|
||||||
|
return // no point continuing
|
||||||
|
}
|
||||||
|
|
||||||
// Service and Check will be nil if tc.wantErr == true && err != nil.
|
// Service and Check will be nil if tc.wantErr == true && err != nil.
|
||||||
// We don't want to panic upon trying to follow a nil pointer, so we
|
// We don't want to panic upon trying to follow a nil pointer, so we
|
||||||
|
@ -2199,118 +1984,12 @@ func TestDecodeCatalogRegister(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/catalog_endpoint.go:
|
|
||||||
// 47
|
|
||||||
// 48 var args structs.DeregisterRequest
|
|
||||||
// 49: if err := decodeBody(req, &args, nil); err != nil {
|
|
||||||
// 50 resp.WriteHeader(http.StatusBadRequest)
|
|
||||||
// 51 fmt.Fprintf(resp, "Request decode failed: %v", err)
|
|
||||||
// ==================================
|
|
||||||
// DeregisterRequest:
|
|
||||||
// Datacenter string
|
|
||||||
// Node string
|
|
||||||
// ServiceID string
|
|
||||||
// CheckID types.CheckID
|
|
||||||
// WriteRequest structs.WriteRequest
|
|
||||||
// Token string
|
|
||||||
func TestDecodeCatalogDeregister(t *testing.T) {
|
|
||||||
t.Skip("DONE. no special fields to parse; no decodeBody callback used.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/config_endpoint.go:
|
|
||||||
// 104
|
|
||||||
// 105 var raw map[string]interface{}
|
|
||||||
// 106: if err := decodeBody(req, &raw, nil); err != nil {
|
|
||||||
// 107 return nil, BadRequestError{Reason: fmt.Sprintf("Request decoding failed: %v", err)}
|
|
||||||
// 108 }
|
|
||||||
// ==================================
|
|
||||||
func TestDecodeConfigApply(t *testing.T) {
|
|
||||||
// TODO $$
|
|
||||||
t.Skip("Leave this fn as-is? Decoding code should probably be the same for all config parsing.")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/connect_ca_endpoint.go:
|
|
||||||
// 63 s.parseDC(req, &args.Datacenter)
|
|
||||||
// 64 s.parseToken(req, &args.Token)
|
|
||||||
// 65: if err := decodeBody(req, &args.Config, nil); err != nil {
|
|
||||||
// 66 resp.WriteHeader(http.StatusBadRequest)
|
|
||||||
// 67 fmt.Fprintf(resp, "Request decode failed: %v", err)
|
|
||||||
// ==================================
|
|
||||||
// CARequest:
|
|
||||||
// Config *structs.CAConfiguration
|
|
||||||
// ClusterID string
|
|
||||||
// Provider string
|
|
||||||
// Config map[string]interface {}
|
|
||||||
// RaftIndex structs.RaftIndex
|
|
||||||
func TestDecodeConnectCAConfigurationSet(t *testing.T) {
|
|
||||||
t.Skip("DONE. no special fields to parse; no decodeBody callback used.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/coordinate_endpoint.go:
|
|
||||||
// 151
|
|
||||||
// 152 args := structs.CoordinateUpdateRequest{}
|
|
||||||
// 153: if err := decodeBody(req, &args, nil); err != nil {
|
|
||||||
// 154 resp.WriteHeader(http.StatusBadRequest)
|
|
||||||
// 155 fmt.Fprintf(resp, "Request decode failed: %v", err)
|
|
||||||
// ==================================
|
|
||||||
// CoordinateUpdateRequest:
|
|
||||||
// Datacenter string
|
|
||||||
// Node string
|
|
||||||
// Segment string
|
|
||||||
// Coord *coordinate.Coordinate
|
|
||||||
// Vec []float64
|
|
||||||
// Error float64
|
|
||||||
// Adjustment float64
|
|
||||||
// Height float64
|
|
||||||
// WriteRequest structs.WriteRequest
|
|
||||||
// Token string
|
|
||||||
func TestDecodeCoordinateUpdate(t *testing.T) {
|
|
||||||
t.Skip("DONE. no special fields to parse; no decodeBody callback used.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/discovery_chain_endpoint.go:
|
|
||||||
// 29 if req.Method == "POST" {
|
|
||||||
// 30 var raw map[string]interface{}
|
|
||||||
// 31: if err := decodeBody(req, &raw, nil); err != nil {
|
|
||||||
// 32 return nil, BadRequestError{Reason: fmt.Sprintf("Request decoding failed: %v", err)}
|
|
||||||
// 33 }
|
|
||||||
// ==================================
|
|
||||||
// discoveryChainReadRequest:
|
// discoveryChainReadRequest:
|
||||||
// OverrideMeshGateway structs.MeshGatewayConfig
|
// OverrideMeshGateway structs.MeshGatewayConfig
|
||||||
// Mode structs.MeshGatewayMode // string
|
// Mode structs.MeshGatewayMode // string
|
||||||
// OverrideProtocol string
|
// OverrideProtocol string
|
||||||
// OverrideConnectTimeout time.Duration
|
// OverrideConnectTimeout time.Duration
|
||||||
func TestDecodeDiscoveryChainRead(t *testing.T) {
|
func TestDecodeDiscoveryChainRead(t *testing.T) {
|
||||||
// Special Beast!
|
|
||||||
|
|
||||||
// This decodeBody call is a special beast, in that it decodes with decodeBody
|
|
||||||
// into a map[string]interface{} and runs subsequent decoding logic outside of
|
|
||||||
// the call.
|
|
||||||
|
|
||||||
// decode code copied from agent/discovery_chain_endpoint.go
|
|
||||||
fullDecodeFn := func(req *http.Request, v *discoveryChainReadRequest) error {
|
|
||||||
var raw map[string]interface{}
|
|
||||||
if err := decodeBody(req, &raw, nil); err != nil {
|
|
||||||
return fmt.Errorf("Request decoding failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
apiReq, err := decodeDiscoveryChainReadRequest(raw)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Request decoding failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
*v = *apiReq
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// It doesn't seem as though mapstructure does weakly typed durations.
|
|
||||||
var weaklyTypedDurationTCs = []translateValueTestCase{
|
var weaklyTypedDurationTCs = []translateValueTestCase{
|
||||||
{
|
{
|
||||||
desc: "positive string integer (weakly typed)",
|
desc: "positive string integer (weakly typed)",
|
||||||
|
@ -2335,11 +2014,9 @@ func TestDecodeDiscoveryChainRead(t *testing.T) {
|
||||||
"OverrideConnectTimeout": %s
|
"OverrideConnectTimeout": %s
|
||||||
}`, tc.durations.in)
|
}`, tc.durations.in)
|
||||||
body := bytes.NewBuffer([]byte(jsonStr))
|
body := bytes.NewBuffer([]byte(jsonStr))
|
||||||
req := httptest.NewRequest("POST", "http://foo.com", body)
|
|
||||||
|
|
||||||
var out discoveryChainReadRequest
|
var out discoveryChainReadRequest
|
||||||
// fullDecodeFn is declared above in this test.
|
err := decodeBody(body, &out)
|
||||||
err := fullDecodeFn(req, &out)
|
|
||||||
if err == nil && tc.wantErr {
|
if err == nil && tc.wantErr {
|
||||||
t.Fatal("expected err, got nil")
|
t.Fatal("expected err, got nil")
|
||||||
}
|
}
|
||||||
|
@ -2371,7 +2048,7 @@ func TestDecodeDiscoveryChainRead(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "bool for string field (weakly typed)",
|
desc: "bool for string field (weakly typed)",
|
||||||
in: `true`,
|
in: `true`,
|
||||||
want: "1",
|
want: "true", // previously: "1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "float for string field (weakly typed)",
|
desc: "float for string field (weakly typed)",
|
||||||
|
@ -2384,9 +2061,9 @@ func TestDecodeDiscoveryChainRead(t *testing.T) {
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "slice for string field (weakly typed)",
|
desc: "slice for string field (weakly typed)",
|
||||||
in: `[]`,
|
in: `[]`,
|
||||||
want: "",
|
wantErr: true, // previously: want: ""
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2394,15 +2071,14 @@ func TestDecodeDiscoveryChainRead(t *testing.T) {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
// set up request body
|
// set up request body
|
||||||
jsonStr := fmt.Sprintf(`{
|
jsonStr := fmt.Sprintf(`{
|
||||||
"OverrideProtocol": %[1]s,
|
"OverrideProtocol": %[1]s,
|
||||||
"OverrideMeshGateway": {"Mode": %[1]s}
|
"OverrideMeshGateway": {"Mode": %[1]s}
|
||||||
}`, tc.in)
|
}`, tc.in)
|
||||||
body := bytes.NewBuffer([]byte(jsonStr))
|
body := bytes.NewBuffer([]byte(jsonStr))
|
||||||
req := httptest.NewRequest("POST", "http://foo.com", body)
|
|
||||||
|
|
||||||
var out discoveryChainReadRequest
|
var out discoveryChainReadRequest
|
||||||
// fullDecodeFn is declared above in this test.
|
err := decodeBody(body, &out)
|
||||||
err := fullDecodeFn(req, &out)
|
|
||||||
if err == nil && tc.wantErr {
|
if err == nil && tc.wantErr {
|
||||||
t.Fatal("expected err, got nil")
|
t.Fatal("expected err, got nil")
|
||||||
}
|
}
|
||||||
|
@ -2552,8 +2228,6 @@ func TestDecodeDiscoveryChainRead(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// from decodeDiscoveryChainReadRequest:
|
|
||||||
//
|
|
||||||
// lib.TranslateKeys(raw, map[string]string{
|
// lib.TranslateKeys(raw, map[string]string{
|
||||||
// "override_mesh_gateway": "overridemeshgateway",
|
// "override_mesh_gateway": "overridemeshgateway",
|
||||||
// "override_protocol": "overrideprotocol",
|
// "override_protocol": "overrideprotocol",
|
||||||
|
@ -2571,11 +2245,9 @@ func TestDecodeDiscoveryChainRead(t *testing.T) {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
jsonStr := fmt.Sprintf(tc.jsonFmtStr, tc.in...)
|
jsonStr := fmt.Sprintf(tc.jsonFmtStr, tc.in...)
|
||||||
body := bytes.NewBuffer([]byte(jsonStr))
|
body := bytes.NewBuffer([]byte(jsonStr))
|
||||||
req := httptest.NewRequest("POST", "http://foo.com", body)
|
|
||||||
|
|
||||||
var out discoveryChainReadRequest
|
var out discoveryChainReadRequest
|
||||||
// fullDecodeFn is declared above in this test.
|
err := decodeBody(body, &out)
|
||||||
err := fullDecodeFn(req, &out)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -2589,14 +2261,6 @@ func TestDecodeDiscoveryChainRead(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/intentions_endpoint.go:
|
|
||||||
// 66 s.parseDC(req, &args.Datacenter)
|
|
||||||
// 67 s.parseToken(req, &args.Token)
|
|
||||||
// 68: if err := decodeBody(req, &args.Intention, fixHashField); err != nil {
|
|
||||||
// 69 return nil, fmt.Errorf("Failed to decode request body: %s", err)
|
|
||||||
// 70 }
|
|
||||||
// ==================================
|
|
||||||
// IntentionRequest:
|
// IntentionRequest:
|
||||||
// Datacenter string
|
// Datacenter string
|
||||||
// Op structs.IntentionOp
|
// Op structs.IntentionOp
|
||||||
|
@ -2639,13 +2303,12 @@ func TestDecodeIntentionCreate(t *testing.T) {
|
||||||
"Hash": %s
|
"Hash": %s
|
||||||
}`, createdAt, updatedAt, hash))
|
}`, createdAt, updatedAt, hash))
|
||||||
|
|
||||||
// set up request
|
|
||||||
body := bytes.NewBuffer(bodyBytes)
|
body := bytes.NewBuffer(bodyBytes)
|
||||||
req := httptest.NewRequest("POST", "http://foo.com", body)
|
|
||||||
|
|
||||||
// decode body
|
// decode body
|
||||||
var out structs.Intention
|
var out structs.Intention
|
||||||
err := decodeBody(req, &out, fixHashField)
|
err := decodeBody(body, &out)
|
||||||
|
|
||||||
if tc.hashes != nil {
|
if tc.hashes != nil {
|
||||||
// We should only check tc.wantErr for hashes in this case.
|
// We should only check tc.wantErr for hashes in this case.
|
||||||
//
|
//
|
||||||
|
@ -2687,44 +2350,6 @@ func TestDecodeIntentionCreate(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/intentions_endpoint.go:
|
|
||||||
// 259 s.parseDC(req, &args.Datacenter)
|
|
||||||
// 260 s.parseToken(req, &args.Token)
|
|
||||||
// 261: if err := decodeBody(req, &args.Intention, fixHashField); err != nil {
|
|
||||||
// 262 return nil, BadRequestError{Reason: fmt.Sprintf("Request decode failed: %v", err)}
|
|
||||||
// 263 }
|
|
||||||
// ==================================
|
|
||||||
func TestDecodeIntentionSpecificUpdate(t *testing.T) {
|
|
||||||
t.Skip("DONE. COVERED BY ABOVE (same structs.Intention)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/operator_endpoint.go:
|
|
||||||
// 77 var args keyringArgs
|
|
||||||
// 78 if req.Method == "POST" || req.Method == "PUT" || req.Method == "DELETE" {
|
|
||||||
// 79: if err := decodeBody(req, &args, nil); err != nil {
|
|
||||||
// 80 return nil, BadRequestError{Reason: fmt.Sprintf("Request decode failed: %v", err)}
|
|
||||||
// 81 }
|
|
||||||
// ==================================
|
|
||||||
// type keyringArgs struct {
|
|
||||||
// Key string
|
|
||||||
// Token string
|
|
||||||
// RelayFactor uint8
|
|
||||||
// LocalOnly bool // ?local-only; only used for GET requests
|
|
||||||
// }
|
|
||||||
func TestDecodeOperatorKeyringEndpoint(t *testing.T) {
|
|
||||||
t.Skip("DONE. no special fields to parse; no decodeBody callback used.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/operator_endpoint.go:
|
|
||||||
// 219 var conf api.AutopilotConfiguration
|
|
||||||
// 220 durations := NewDurationFixer("lastcontactthreshold", "serverstabilizationtime")
|
|
||||||
// 221: if err := decodeBody(req, &conf, durations.FixupDurations); err != nil {
|
|
||||||
// 222 return nil, BadRequestError{Reason: fmt.Sprintf("Error parsing autopilot config: %v", err)}
|
|
||||||
// 223 }
|
|
||||||
// ==================================
|
|
||||||
// AutopilotConfiguration:
|
// AutopilotConfiguration:
|
||||||
// CleanupDeadServers bool
|
// CleanupDeadServers bool
|
||||||
// LastContactThreshold *api.ReadableDuration
|
// LastContactThreshold *api.ReadableDuration
|
||||||
|
@ -2745,10 +2370,10 @@ func TestDecodeOperatorAutopilotConfiguration(t *testing.T) {
|
||||||
}`, tc.durations.in)
|
}`, tc.durations.in)
|
||||||
|
|
||||||
body := bytes.NewBuffer([]byte(jsonStr))
|
body := bytes.NewBuffer([]byte(jsonStr))
|
||||||
req := httptest.NewRequest("POST", "http://foo.com", body)
|
|
||||||
|
|
||||||
var out api.AutopilotConfiguration
|
var out api.AutopilotConfiguration
|
||||||
err := decodeBody(req, &out, durations.FixupDurations)
|
err := decodeBody(body, &out)
|
||||||
|
|
||||||
if err == nil && tc.wantErr {
|
if err == nil && tc.wantErr {
|
||||||
t.Fatal("expected err, got nil")
|
t.Fatal("expected err, got nil")
|
||||||
}
|
}
|
||||||
|
@ -2774,69 +2399,6 @@ func TestDecodeOperatorAutopilotConfiguration(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/prepared_query_endpoint.go:
|
|
||||||
// 24 s.parseDC(req, &args.Datacenter)
|
|
||||||
// 25 s.parseToken(req, &args.Token)
|
|
||||||
// 26: if err := decodeBody(req, &args.Query, nil); err != nil {
|
|
||||||
// 27 resp.WriteHeader(http.StatusBadRequest)
|
|
||||||
// 28 fmt.Fprintf(resp, "Request decode failed: %v", err)
|
|
||||||
// ==================================
|
|
||||||
// PreparedQueryRequest:
|
|
||||||
// Datacenter string
|
|
||||||
// Op structs.PreparedQueryOp
|
|
||||||
// Query *structs.PreparedQuery
|
|
||||||
// ID string
|
|
||||||
// Name string
|
|
||||||
// Session string
|
|
||||||
// Token string
|
|
||||||
// Template structs.QueryTemplateOptions
|
|
||||||
// Type string
|
|
||||||
// Regexp string
|
|
||||||
// RemoveEmptyTags bool
|
|
||||||
// Service structs.ServiceQuery
|
|
||||||
// Service string
|
|
||||||
// Failover structs.QueryDatacenterOptions
|
|
||||||
// NearestN int
|
|
||||||
// Datacenters []string
|
|
||||||
// OnlyPassing bool
|
|
||||||
// IgnoreCheckIDs []types.CheckID
|
|
||||||
// Near string
|
|
||||||
// Tags []string
|
|
||||||
// NodeMeta map[string]string
|
|
||||||
// ServiceMeta map[string]string
|
|
||||||
// Connect bool
|
|
||||||
// DNS structs.QueryDNSOptions
|
|
||||||
// TTL string
|
|
||||||
// RaftIndex structs.RaftIndex
|
|
||||||
// CreateIndex uint64
|
|
||||||
// ModifyIndex uint64
|
|
||||||
// WriteRequest structs.WriteRequest
|
|
||||||
// Token string
|
|
||||||
func TestDecodePreparedQueryGeneral_Create(t *testing.T) {
|
|
||||||
t.Skip("DONE. no special fields to parse; no decodeBody callback used.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/prepared_query_endpoint.go:
|
|
||||||
// 254 s.parseToken(req, &args.Token)
|
|
||||||
// 255 if req.ContentLength > 0 {
|
|
||||||
// 256: if err := decodeBody(req, &args.Query, nil); err != nil {
|
|
||||||
// 257 resp.WriteHeader(http.StatusBadRequest)
|
|
||||||
// 258 fmt.Fprintf(resp, "Request decode failed: %v", err)
|
|
||||||
// ==================================
|
|
||||||
func TestDecodePreparedQueryGeneral_Update(t *testing.T) {
|
|
||||||
t.Skip("DONE. COVERED BY ABOVE (same structs.PreparedQuery)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/session_endpoint.go:
|
|
||||||
// 54 return nil
|
|
||||||
// 55 }
|
|
||||||
// 56: if err := decodeBody(req, &args.Session, fixup); err != nil {
|
|
||||||
// 57 resp.WriteHeader(http.StatusBadRequest)
|
|
||||||
// 58 fmt.Fprintf(resp, "Request decode failed: %v", err)
|
|
||||||
// ==================================
|
|
||||||
// SessionRequest:
|
// SessionRequest:
|
||||||
// Datacenter string
|
// Datacenter string
|
||||||
// Op structs.SessionOp
|
// Op structs.SessionOp
|
||||||
|
@ -2854,22 +2416,10 @@ func TestDecodePreparedQueryGeneral_Update(t *testing.T) {
|
||||||
// WriteRequest structs.WriteRequest
|
// WriteRequest structs.WriteRequest
|
||||||
// Token string
|
// Token string
|
||||||
func TestDecodeSessionCreate(t *testing.T) {
|
func TestDecodeSessionCreate(t *testing.T) {
|
||||||
|
|
||||||
// outSession var is shared among test cases b/c of the
|
// outSession var is shared among test cases b/c of the
|
||||||
// nature/signature of the FixupChecks callback.
|
// nature/signature of the FixupChecks callback.
|
||||||
var outSession structs.Session
|
var outSession structs.Session
|
||||||
|
|
||||||
// copied from agent/session_endpoint.go
|
|
||||||
fixupCB := func(raw interface{}) error {
|
|
||||||
if err := FixupLockDelay(raw); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := FixupChecks(raw, &outSession); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// lockDelayMinThreshold = 1000
|
// lockDelayMinThreshold = 1000
|
||||||
|
|
||||||
sessionDurationTCs := append(positiveDurationTCs,
|
sessionDurationTCs := append(positiveDurationTCs,
|
||||||
|
@ -2901,25 +2451,6 @@ func TestDecodeSessionCreate(t *testing.T) {
|
||||||
want: -5 * time.Second,
|
want: -5 * time.Second,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// // Test cases that illicit bad behavior; Don't run them.
|
|
||||||
// translateValueTestCase{
|
|
||||||
// desc: "durations large, numeric and negative",
|
|
||||||
// durations: &durationTC{
|
|
||||||
// in: `-2000`,
|
|
||||||
// want: time.Duration(-2000),
|
|
||||||
// },
|
|
||||||
// // --- FAIL: TestDecodeSessionCreate/durations_large,_numeric_and_negative (0.00s)
|
|
||||||
// // http_decode_test.go:2665: expected LockDelay to be -2µs, got -33m20s
|
|
||||||
// },
|
|
||||||
// translateValueTestCase{
|
|
||||||
// desc: "durations string, negative",
|
|
||||||
// durations: &durationTC{
|
|
||||||
// in: `"-50ms"`,
|
|
||||||
// want: -50 * time.Millisecond,
|
|
||||||
// },
|
|
||||||
// // --- FAIL: TestDecodeSessionCreate/durations_string,_negative (0.00s)
|
|
||||||
// // http_decode_test.go:2665: expected LockDelay to be -50ms, got -13888h53m20s
|
|
||||||
// },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, tc := range sessionDurationTCs {
|
for _, tc := range sessionDurationTCs {
|
||||||
|
@ -2935,10 +2466,10 @@ func TestDecodeSessionCreate(t *testing.T) {
|
||||||
}`, tc.durations.in)
|
}`, tc.durations.in)
|
||||||
|
|
||||||
body := bytes.NewBuffer([]byte(jsonStr))
|
body := bytes.NewBuffer([]byte(jsonStr))
|
||||||
req := httptest.NewRequest("POST", "http://foo.com", body)
|
|
||||||
|
|
||||||
// outSession var is shared among test cases
|
// outSession var is shared among test cases
|
||||||
err := decodeBody(req, &outSession, fixupCB)
|
|
||||||
|
err := decodeBody(body, &outSession)
|
||||||
if err == nil && tc.wantErr {
|
if err == nil && tc.wantErr {
|
||||||
t.Fatal("expected err, got nil")
|
t.Fatal("expected err, got nil")
|
||||||
}
|
}
|
||||||
|
@ -3002,10 +2533,8 @@ func TestDecodeSessionCreate(t *testing.T) {
|
||||||
}`, tc.in)
|
}`, tc.in)
|
||||||
|
|
||||||
body := bytes.NewBuffer([]byte(jsonStr))
|
body := bytes.NewBuffer([]byte(jsonStr))
|
||||||
req := httptest.NewRequest("POST", "http://foo.com", body)
|
|
||||||
|
|
||||||
// outSession var is shared among test cases
|
err := decodeBody(body, &outSession)
|
||||||
err := decodeBody(req, &outSession, fixupCB)
|
|
||||||
if err == nil && tc.wantErr {
|
if err == nil && tc.wantErr {
|
||||||
t.Fatal("expected err, got nil")
|
t.Fatal("expected err, got nil")
|
||||||
}
|
}
|
||||||
|
@ -3022,17 +2551,8 @@ func TestDecodeSessionCreate(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/txn_endpoint.go:
|
|
||||||
// 116 // associate the error with a given operation.
|
|
||||||
// 117 var ops api.TxnOps
|
|
||||||
// 118: if err := decodeBody(req, &ops, fixupTxnOps); err != nil {
|
|
||||||
// 119 resp.WriteHeader(http.StatusBadRequest)
|
|
||||||
// 120 fmt.Fprintf(resp, "Failed to parse body: %v", err)
|
|
||||||
// ==================================
|
|
||||||
// TxnOps:
|
// TxnOps:
|
||||||
// KV *api.KVTxnOp
|
// KV *api.KVTxnOp
|
||||||
// Verb api.KVOp
|
// Verb api.KVOp
|
||||||
|
@ -3181,10 +2701,10 @@ func TestDecodeTxnConvertOps(t *testing.T) {
|
||||||
}]`, tc.durations.in)
|
}]`, tc.durations.in)
|
||||||
|
|
||||||
body := bytes.NewBuffer([]byte(jsonStr))
|
body := bytes.NewBuffer([]byte(jsonStr))
|
||||||
req := httptest.NewRequest("POST", "http://foo.com", body)
|
|
||||||
|
|
||||||
var out api.TxnOps
|
var out api.TxnOps
|
||||||
err := decodeBody(req, &out, fixupTxnOps)
|
err := decodeBody(body, &out)
|
||||||
|
|
||||||
if err == nil && tc.wantErr {
|
if err == nil && tc.wantErr {
|
||||||
t.Fatal("expected err, got nil")
|
t.Fatal("expected err, got nil")
|
||||||
}
|
}
|
||||||
|
@ -3226,20 +2746,6 @@ func TestDecodeTxnConvertOps(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =======================================
|
|
||||||
// Benchmarks:
|
|
||||||
// ==================================
|
|
||||||
// $GOPATH/github.com/hashicorp/consul/agent/http.go:
|
|
||||||
// 574
|
|
||||||
// 575 // decodeBody is used to decode a JSON request body
|
|
||||||
// 576: func decodeBody(req *http.Request, out interface{}, cb func(interface{}) error) error {
|
|
||||||
// 577 // This generally only happens in tests since real HTTP requests set
|
|
||||||
// 578 // a non-nil body with no content. We guard against it anyways to prevent
|
|
||||||
// ==================================
|
|
||||||
func BenchmarkDecodeBody(b *testing.B) {
|
|
||||||
b.Skip() // TODO: benchmark
|
|
||||||
}
|
|
||||||
|
|
||||||
// =========================================
|
// =========================================
|
||||||
// Helper funcs:
|
// Helper funcs:
|
||||||
// =========================================
|
// =========================================
|
||||||
|
|
|
@ -9,21 +9,6 @@ import (
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// fixHashField is used to convert the JSON string to a []byte before handing to mapstructure
|
|
||||||
func fixHashField(raw interface{}) error {
|
|
||||||
rawMap, ok := raw.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if val, ok := rawMap["Hash"]; ok {
|
|
||||||
if sval, ok := val.(string); ok {
|
|
||||||
rawMap["Hash"] = []byte(sval)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// /v1/connection/intentions
|
// /v1/connection/intentions
|
||||||
func (s *HTTPServer) IntentionEndpoint(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPServer) IntentionEndpoint(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
|
@ -65,7 +50,7 @@ func (s *HTTPServer) IntentionCreate(resp http.ResponseWriter, req *http.Request
|
||||||
}
|
}
|
||||||
s.parseDC(req, &args.Datacenter)
|
s.parseDC(req, &args.Datacenter)
|
||||||
s.parseToken(req, &args.Token)
|
s.parseToken(req, &args.Token)
|
||||||
if err := decodeBody(req, &args.Intention, fixHashField); err != nil {
|
if err := decodeBody(req.Body, &args.Intention); err != nil {
|
||||||
return nil, fmt.Errorf("Failed to decode request body: %s", err)
|
return nil, fmt.Errorf("Failed to decode request body: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,7 +243,7 @@ func (s *HTTPServer) IntentionSpecificUpdate(id string, resp http.ResponseWriter
|
||||||
}
|
}
|
||||||
s.parseDC(req, &args.Datacenter)
|
s.parseDC(req, &args.Datacenter)
|
||||||
s.parseToken(req, &args.Token)
|
s.parseToken(req, &args.Token)
|
||||||
if err := decodeBody(req, &args.Intention, fixHashField); err != nil {
|
if err := decodeBody(req.Body, &args.Intention); err != nil {
|
||||||
return nil, BadRequestError{Reason: fmt.Sprintf("Request decode failed: %v", err)}
|
return nil, BadRequestError{Reason: fmt.Sprintf("Request decode failed: %v", err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ type keyringArgs struct {
|
||||||
func (s *HTTPServer) OperatorKeyringEndpoint(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPServer) OperatorKeyringEndpoint(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
var args keyringArgs
|
var args keyringArgs
|
||||||
if req.Method == "POST" || req.Method == "PUT" || req.Method == "DELETE" {
|
if req.Method == "POST" || req.Method == "PUT" || req.Method == "DELETE" {
|
||||||
if err := decodeBody(req, &args, nil); err != nil {
|
if err := decodeBody(req.Body, &args); err != nil {
|
||||||
return nil, BadRequestError{Reason: fmt.Sprintf("Request decode failed: %v", err)}
|
return nil, BadRequestError{Reason: fmt.Sprintf("Request decode failed: %v", err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,8 +218,7 @@ func (s *HTTPServer) OperatorAutopilotConfiguration(resp http.ResponseWriter, re
|
||||||
s.parseToken(req, &args.Token)
|
s.parseToken(req, &args.Token)
|
||||||
|
|
||||||
var conf api.AutopilotConfiguration
|
var conf api.AutopilotConfiguration
|
||||||
durations := NewDurationFixer("lastcontactthreshold", "serverstabilizationtime")
|
if err := decodeBody(req.Body, &conf); err != nil {
|
||||||
if err := decodeBody(req, &conf, durations.FixupDurations); err != nil {
|
|
||||||
return nil, BadRequestError{Reason: fmt.Sprintf("Error parsing autopilot config: %v", err)}
|
return nil, BadRequestError{Reason: fmt.Sprintf("Error parsing autopilot config: %v", err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ func (s *HTTPServer) preparedQueryCreate(resp http.ResponseWriter, req *http.Req
|
||||||
}
|
}
|
||||||
s.parseDC(req, &args.Datacenter)
|
s.parseDC(req, &args.Datacenter)
|
||||||
s.parseToken(req, &args.Token)
|
s.parseToken(req, &args.Token)
|
||||||
if err := decodeBody(req, &args.Query, nil); err != nil {
|
if err := decodeBody(req.Body, &args.Query); err != nil {
|
||||||
resp.WriteHeader(http.StatusBadRequest)
|
resp.WriteHeader(http.StatusBadRequest)
|
||||||
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -253,7 +253,7 @@ func (s *HTTPServer) preparedQueryUpdate(id string, resp http.ResponseWriter, re
|
||||||
s.parseDC(req, &args.Datacenter)
|
s.parseDC(req, &args.Datacenter)
|
||||||
s.parseToken(req, &args.Token)
|
s.parseToken(req, &args.Token)
|
||||||
if req.ContentLength > 0 {
|
if req.ContentLength > 0 {
|
||||||
if err := decodeBody(req, &args.Query, nil); err != nil {
|
if err := decodeBody(req.Body, &args.Query); err != nil {
|
||||||
resp.WriteHeader(http.StatusBadRequest)
|
resp.WriteHeader(http.StatusBadRequest)
|
||||||
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
@ -10,16 +10,6 @@ import (
|
||||||
"github.com/hashicorp/consul/types"
|
"github.com/hashicorp/consul/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// lockDelayMinThreshold is used to convert a numeric lock
|
|
||||||
// delay value from nanoseconds to seconds if it is below this
|
|
||||||
// threshold. Users often send a value like 5, which they assume
|
|
||||||
// is seconds, but because Go uses nanosecond granularity, ends
|
|
||||||
// up being very small. If we see a value below this threshold,
|
|
||||||
// we multiply by time.Second
|
|
||||||
lockDelayMinThreshold = 1000
|
|
||||||
)
|
|
||||||
|
|
||||||
// sessionCreateResponse is used to wrap the session ID
|
// sessionCreateResponse is used to wrap the session ID
|
||||||
type sessionCreateResponse struct {
|
type sessionCreateResponse struct {
|
||||||
ID string
|
ID string
|
||||||
|
@ -44,16 +34,7 @@ func (s *HTTPServer) SessionCreate(resp http.ResponseWriter, req *http.Request)
|
||||||
|
|
||||||
// Handle optional request body
|
// Handle optional request body
|
||||||
if req.ContentLength > 0 {
|
if req.ContentLength > 0 {
|
||||||
fixup := func(raw interface{}) error {
|
if err := decodeBody(req.Body, &args.Session); err != nil {
|
||||||
if err := FixupLockDelay(raw); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := FixupChecks(raw, &args.Session); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := decodeBody(req, &args.Session, fixup); err != nil {
|
|
||||||
resp.WriteHeader(http.StatusBadRequest)
|
resp.WriteHeader(http.StatusBadRequest)
|
||||||
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -70,45 +51,6 @@ func (s *HTTPServer) SessionCreate(resp http.ResponseWriter, req *http.Request)
|
||||||
return sessionCreateResponse{out}, nil
|
return sessionCreateResponse{out}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FixupLockDelay is used to handle parsing the JSON body to session/create
|
|
||||||
// and properly parsing out the lock delay duration value.
|
|
||||||
func FixupLockDelay(raw interface{}) error {
|
|
||||||
rawMap, ok := raw.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var key string
|
|
||||||
for k := range rawMap {
|
|
||||||
if strings.ToLower(k) == "lockdelay" {
|
|
||||||
key = k
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if key != "" {
|
|
||||||
val := rawMap[key]
|
|
||||||
// Convert a string value into an integer
|
|
||||||
if vStr, ok := val.(string); ok {
|
|
||||||
dur, err := time.ParseDuration(vStr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if dur < lockDelayMinThreshold {
|
|
||||||
dur = dur * time.Second
|
|
||||||
}
|
|
||||||
rawMap[key] = dur
|
|
||||||
}
|
|
||||||
// Convert low value integers into seconds
|
|
||||||
if vNum, ok := val.(float64); ok {
|
|
||||||
dur := time.Duration(vNum)
|
|
||||||
if dur < lockDelayMinThreshold {
|
|
||||||
dur = dur * time.Second
|
|
||||||
}
|
|
||||||
rawMap[key] = dur
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FixupChecks is used to handle parsing the JSON body to default-add the Serf
|
// FixupChecks is used to handle parsing the JSON body to default-add the Serf
|
||||||
// health check if they didn't specify any checks, but to allow an empty list
|
// health check if they didn't specify any checks, but to allow an empty list
|
||||||
// to take out the Serf health check. This behavior broke when mapstructure was
|
// to take out the Serf health check. This behavior broke when mapstructure was
|
||||||
|
|
|
@ -223,39 +223,6 @@ func TestSessionCreate_NoCheck(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFixupLockDelay(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
inp := map[string]interface{}{
|
|
||||||
"lockdelay": float64(15),
|
|
||||||
}
|
|
||||||
if err := FixupLockDelay(inp); err != nil {
|
|
||||||
t.Fatalf("err: %v", err)
|
|
||||||
}
|
|
||||||
if inp["lockdelay"] != 15*time.Second {
|
|
||||||
t.Fatalf("bad: %v", inp)
|
|
||||||
}
|
|
||||||
|
|
||||||
inp = map[string]interface{}{
|
|
||||||
"lockDelay": float64(15 * time.Second),
|
|
||||||
}
|
|
||||||
if err := FixupLockDelay(inp); err != nil {
|
|
||||||
t.Fatalf("err: %v", err)
|
|
||||||
}
|
|
||||||
if inp["lockDelay"] != 15*time.Second {
|
|
||||||
t.Fatalf("bad: %v", inp)
|
|
||||||
}
|
|
||||||
|
|
||||||
inp = map[string]interface{}{
|
|
||||||
"LockDelay": "15s",
|
|
||||||
}
|
|
||||||
if err := FixupLockDelay(inp); err != nil {
|
|
||||||
t.Fatalf("err: %v", err)
|
|
||||||
}
|
|
||||||
if inp["LockDelay"] != 15*time.Second {
|
|
||||||
t.Fatalf("bad: %v", inp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeTestSession(t *testing.T, srv *HTTPServer) string {
|
func makeTestSession(t *testing.T, srv *HTTPServer) string {
|
||||||
req, _ := http.NewRequest("PUT", "/v1/session/create", nil)
|
req, _ := http.NewRequest("PUT", "/v1/session/create", nil)
|
||||||
resp := httptest.NewRecorder()
|
resp := httptest.NewRecorder()
|
||||||
|
|
|
@ -2,6 +2,7 @@ package structs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
|
@ -260,6 +261,35 @@ type ACLToken struct {
|
||||||
RaftIndex
|
RaftIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *ACLToken) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
type Alias ACLToken
|
||||||
|
aux := &struct {
|
||||||
|
ExpirationTTL interface{}
|
||||||
|
Hash string
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(t),
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(data, &aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if aux.ExpirationTTL != nil {
|
||||||
|
switch v := aux.ExpirationTTL.(type) {
|
||||||
|
case string:
|
||||||
|
if t.ExpirationTTL, err = time.ParseDuration(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
t.ExpirationTTL = time.Duration(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if aux.Hash != "" {
|
||||||
|
t.Hash = []byte(aux.Hash)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (t *ACLToken) Clone() *ACLToken {
|
func (t *ACLToken) Clone() *ACLToken {
|
||||||
t2 := *t
|
t2 := *t
|
||||||
t2.Policies = nil
|
t2.Policies = nil
|
||||||
|
@ -539,6 +569,23 @@ type ACLPolicy struct {
|
||||||
RaftIndex `hash:"ignore"`
|
RaftIndex `hash:"ignore"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *ACLPolicy) UnmarshalJSON(data []byte) error {
|
||||||
|
type Alias ACLPolicy
|
||||||
|
aux := &struct {
|
||||||
|
Hash string
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(t),
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if aux.Hash != "" {
|
||||||
|
t.Hash = []byte(aux.Hash)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *ACLPolicy) Clone() *ACLPolicy {
|
func (p *ACLPolicy) Clone() *ACLPolicy {
|
||||||
p2 := *p
|
p2 := *p
|
||||||
p2.Datacenters = cloneStringSlice(p.Datacenters)
|
p2.Datacenters = cloneStringSlice(p.Datacenters)
|
||||||
|
@ -768,6 +815,23 @@ type ACLRole struct {
|
||||||
RaftIndex `hash:"ignore"`
|
RaftIndex `hash:"ignore"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *ACLRole) UnmarshalJSON(data []byte) error {
|
||||||
|
type Alias ACLRole
|
||||||
|
aux := &struct {
|
||||||
|
Hash string
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(t),
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if aux.Hash != "" {
|
||||||
|
t.Hash = []byte(aux.Hash)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *ACLRole) Clone() *ACLRole {
|
func (r *ACLRole) Clone() *ACLRole {
|
||||||
r2 := *r
|
r2 := *r
|
||||||
r2.Policies = nil
|
r2.Policies = nil
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package structs
|
package structs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
|
@ -42,6 +43,98 @@ type CheckDefinition struct {
|
||||||
OutputMaxSize int
|
OutputMaxSize int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *CheckDefinition) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
type Alias CheckDefinition
|
||||||
|
aux := &struct {
|
||||||
|
// Parse special values
|
||||||
|
Interval interface{}
|
||||||
|
Timeout interface{}
|
||||||
|
TTL interface{}
|
||||||
|
DeregisterCriticalServiceAfter interface{}
|
||||||
|
|
||||||
|
// Translate fields
|
||||||
|
|
||||||
|
// "args" -> ScriptArgs
|
||||||
|
Args []string `json:"args"`
|
||||||
|
ScriptArgsSnake []string `json:"script_args"`
|
||||||
|
DeregisterCriticalServiceAfterSnake interface{} `json:"deregister_critical_service_after"`
|
||||||
|
DockerContainerIDSnake string `json:"docker_container_id"`
|
||||||
|
TLSSkipVerifySnake bool `json:"tls_skip_verify"`
|
||||||
|
ServiceIDSnake string `json:"service_id"`
|
||||||
|
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(t),
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(data, &aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate Fields
|
||||||
|
if aux.DeregisterCriticalServiceAfter == nil {
|
||||||
|
aux.DeregisterCriticalServiceAfter = aux.DeregisterCriticalServiceAfterSnake
|
||||||
|
}
|
||||||
|
if len(t.ScriptArgs) == 0 {
|
||||||
|
t.ScriptArgs = aux.Args
|
||||||
|
}
|
||||||
|
if len(t.ScriptArgs) == 0 {
|
||||||
|
t.ScriptArgs = aux.ScriptArgsSnake
|
||||||
|
}
|
||||||
|
if t.DockerContainerID == "" {
|
||||||
|
t.DockerContainerID = aux.DockerContainerIDSnake
|
||||||
|
}
|
||||||
|
if aux.TLSSkipVerifySnake {
|
||||||
|
t.TLSSkipVerify = aux.TLSSkipVerifySnake
|
||||||
|
}
|
||||||
|
if t.ServiceID == "" {
|
||||||
|
t.ServiceID = aux.ServiceIDSnake
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse special values
|
||||||
|
if aux.Interval != nil {
|
||||||
|
switch v := aux.Interval.(type) {
|
||||||
|
case string:
|
||||||
|
if t.Interval, err = time.ParseDuration(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
t.Interval = time.Duration(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if aux.Timeout != nil {
|
||||||
|
switch v := aux.Timeout.(type) {
|
||||||
|
case string:
|
||||||
|
if t.Timeout, err = time.ParseDuration(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
t.Timeout = time.Duration(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if aux.TTL != nil {
|
||||||
|
switch v := aux.TTL.(type) {
|
||||||
|
case string:
|
||||||
|
if t.TTL, err = time.ParseDuration(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
t.TTL = time.Duration(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if aux.DeregisterCriticalServiceAfter != nil {
|
||||||
|
switch v := aux.DeregisterCriticalServiceAfter.(type) {
|
||||||
|
case string:
|
||||||
|
if t.DeregisterCriticalServiceAfter, err = time.ParseDuration(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
t.DeregisterCriticalServiceAfter = time.Duration(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *CheckDefinition) HealthCheck(node string) *HealthCheck {
|
func (c *CheckDefinition) HealthCheck(node string) *HealthCheck {
|
||||||
health := &HealthCheck{
|
health := &HealthCheck{
|
||||||
Node: node,
|
Node: node,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package structs
|
package structs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
@ -8,6 +9,8 @@ import (
|
||||||
"github.com/hashicorp/consul/types"
|
"github.com/hashicorp/consul/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type CheckTypes []*CheckType
|
||||||
|
|
||||||
// CheckType is used to create either the CheckMonitor or the CheckTTL.
|
// CheckType is used to create either the CheckMonitor or the CheckTTL.
|
||||||
// The following types are supported: Script, HTTP, TCP, Docker, TTL, GRPC, Alias. Script,
|
// The following types are supported: Script, HTTP, TCP, Docker, TTL, GRPC, Alias. Script,
|
||||||
// HTTP, Docker, TCP and GRPC all require Interval. Only one of the types may
|
// HTTP, Docker, TCP and GRPC all require Interval. Only one of the types may
|
||||||
|
@ -55,7 +58,91 @@ type CheckType struct {
|
||||||
DeregisterCriticalServiceAfter time.Duration
|
DeregisterCriticalServiceAfter time.Duration
|
||||||
OutputMaxSize int
|
OutputMaxSize int
|
||||||
}
|
}
|
||||||
type CheckTypes []*CheckType
|
|
||||||
|
func (t *CheckType) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
type Alias CheckType
|
||||||
|
aux := &struct {
|
||||||
|
Interval interface{}
|
||||||
|
Timeout interface{}
|
||||||
|
TTL interface{}
|
||||||
|
DeregisterCriticalServiceAfter interface{}
|
||||||
|
|
||||||
|
// Translate fields
|
||||||
|
|
||||||
|
// "args" -> ScriptArgs
|
||||||
|
Args []string `json:"args"`
|
||||||
|
ScriptArgsSnake []string `json:"script_args"`
|
||||||
|
DeregisterCriticalServiceAfterSnake interface{} `json:"deregister_critical_service_after"`
|
||||||
|
DockerContainerIDSnake string `json:"docker_container_id"`
|
||||||
|
TLSSkipVerifySnake bool `json:"tls_skip_verify"`
|
||||||
|
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(t),
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(data, aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if aux.DeregisterCriticalServiceAfter == nil {
|
||||||
|
aux.DeregisterCriticalServiceAfter = aux.DeregisterCriticalServiceAfterSnake
|
||||||
|
}
|
||||||
|
if len(t.ScriptArgs) == 0 {
|
||||||
|
t.ScriptArgs = aux.Args
|
||||||
|
}
|
||||||
|
if len(t.ScriptArgs) == 0 {
|
||||||
|
t.ScriptArgs = aux.ScriptArgsSnake
|
||||||
|
}
|
||||||
|
if t.DockerContainerID == "" {
|
||||||
|
t.DockerContainerID = aux.DockerContainerIDSnake
|
||||||
|
}
|
||||||
|
if aux.TLSSkipVerifySnake {
|
||||||
|
t.TLSSkipVerify = aux.TLSSkipVerifySnake
|
||||||
|
}
|
||||||
|
|
||||||
|
if aux.Interval != nil {
|
||||||
|
switch v := aux.Interval.(type) {
|
||||||
|
case string:
|
||||||
|
if t.Interval, err = time.ParseDuration(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
t.Interval = time.Duration(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if aux.Timeout != nil {
|
||||||
|
switch v := aux.Timeout.(type) {
|
||||||
|
case string:
|
||||||
|
if t.Timeout, err = time.ParseDuration(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
t.Timeout = time.Duration(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if aux.TTL != nil {
|
||||||
|
switch v := aux.TTL.(type) {
|
||||||
|
case string:
|
||||||
|
if t.TTL, err = time.ParseDuration(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
t.TTL = time.Duration(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if aux.DeregisterCriticalServiceAfter != nil {
|
||||||
|
switch v := aux.DeregisterCriticalServiceAfter.(type) {
|
||||||
|
case string:
|
||||||
|
if t.DeregisterCriticalServiceAfter, err = time.ParseDuration(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
t.DeregisterCriticalServiceAfter = time.Duration(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Validate returns an error message if the check is invalid
|
// Validate returns an error message if the check is invalid
|
||||||
func (c *CheckType) Validate() error {
|
func (c *CheckType) Validate() error {
|
||||||
|
|
|
@ -3,9 +3,10 @@ package structs
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
"github.com/hashicorp/consul/lib"
|
"github.com/hashicorp/consul/lib"
|
||||||
"log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -120,6 +121,38 @@ type ConnectProxyConfig struct {
|
||||||
Expose ExposeConfig `json:",omitempty"`
|
Expose ExposeConfig `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *ConnectProxyConfig) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
type Alias ConnectProxyConfig
|
||||||
|
aux := &struct {
|
||||||
|
DestinationServiceNameSnake string `json:"destination_service_name"`
|
||||||
|
DestinationServiceIDSnake string `json:"destination_service_id"`
|
||||||
|
LocalServiceAddressSnake string `json:"local_service_address"`
|
||||||
|
LocalServicePortSnake int `json:"local_service_port"`
|
||||||
|
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(t),
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(data, &aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if t.DestinationServiceName == "" {
|
||||||
|
t.DestinationServiceName = aux.DestinationServiceNameSnake
|
||||||
|
}
|
||||||
|
if t.DestinationServiceID == "" {
|
||||||
|
t.DestinationServiceID = aux.DestinationServiceIDSnake
|
||||||
|
}
|
||||||
|
if t.LocalServiceAddress == "" {
|
||||||
|
t.LocalServiceAddress = aux.LocalServiceAddressSnake
|
||||||
|
}
|
||||||
|
if t.LocalServicePort == 0 {
|
||||||
|
t.LocalServicePort = aux.LocalServicePortSnake
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ConnectProxyConfig) MarshalJSON() ([]byte, error) {
|
func (c *ConnectProxyConfig) MarshalJSON() ([]byte, error) {
|
||||||
type typeCopy ConnectProxyConfig
|
type typeCopy ConnectProxyConfig
|
||||||
copy := typeCopy(*c)
|
copy := typeCopy(*c)
|
||||||
|
@ -217,6 +250,41 @@ type Upstream struct {
|
||||||
MeshGateway MeshGatewayConfig `json:",omitempty"`
|
MeshGateway MeshGatewayConfig `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Upstream) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
type Alias Upstream
|
||||||
|
aux := &struct {
|
||||||
|
DestinationTypeSnake string `json:"destination_type"`
|
||||||
|
DestinationNamespaceSnake string `json:"destination_namespace"`
|
||||||
|
DestinationNameSnake string `json:"destination_name"`
|
||||||
|
LocalBindPortSnake int `json:"local_bind_port"`
|
||||||
|
LocalBindAddressSnake string `json:"local_bind_address"`
|
||||||
|
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(t),
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(data, &aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if t.DestinationType == "" {
|
||||||
|
t.DestinationType = aux.DestinationTypeSnake
|
||||||
|
}
|
||||||
|
if t.DestinationNamespace == "" {
|
||||||
|
t.DestinationNamespace = aux.DestinationNamespaceSnake
|
||||||
|
}
|
||||||
|
if t.DestinationName == "" {
|
||||||
|
t.DestinationName = aux.DestinationNameSnake
|
||||||
|
}
|
||||||
|
if t.LocalBindPort == 0 {
|
||||||
|
t.LocalBindPort = aux.LocalBindPortSnake
|
||||||
|
}
|
||||||
|
if t.LocalBindAddress == "" {
|
||||||
|
t.LocalBindAddress = aux.LocalBindAddressSnake
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Validate sanity checks the struct is valid
|
// Validate sanity checks the struct is valid
|
||||||
func (u *Upstream) Validate() error {
|
func (u *Upstream) Validate() error {
|
||||||
switch u.DestinationType {
|
switch u.DestinationType {
|
||||||
|
@ -352,6 +420,29 @@ type ExposePath struct {
|
||||||
ParsedFromCheck bool
|
ParsedFromCheck bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *ExposePath) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
type Alias ExposePath
|
||||||
|
aux := &struct {
|
||||||
|
LocalPathPortSnake int `json:"local_path_port"`
|
||||||
|
ListenerPortSnake int `json:"listener_port"`
|
||||||
|
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(t),
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(data, &aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if t.LocalPathPort == 0 {
|
||||||
|
t.LocalPathPort = aux.LocalPathPortSnake
|
||||||
|
}
|
||||||
|
if t.ListenerPort == 0 {
|
||||||
|
t.ListenerPort = aux.ListenerPortSnake
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (e *ExposeConfig) ToAPI() api.ExposeConfig {
|
func (e *ExposeConfig) ToAPI() api.ExposeConfig {
|
||||||
paths := make([]api.ExposePath, 0)
|
paths := make([]api.ExposePath, 0)
|
||||||
for _, p := range e.Paths {
|
for _, p := range e.Paths {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package structs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -84,6 +85,26 @@ type Intention struct {
|
||||||
RaftIndex
|
RaftIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Intention) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
type Alias Intention
|
||||||
|
aux := &struct {
|
||||||
|
Hash string
|
||||||
|
CreatedAt, UpdatedAt string // effectively `json:"-"` on Intention type
|
||||||
|
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(t),
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(data, &aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if aux.Hash != "" {
|
||||||
|
t.Hash = []byte(aux.Hash)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (x *Intention) SetHash(force bool) []byte {
|
func (x *Intention) SetHash(force bool) []byte {
|
||||||
if force || x.Hash == nil {
|
if force || x.Hash == nil {
|
||||||
hash, err := blake2b.New256(nil)
|
hash, err := blake2b.New256(nil)
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package structs
|
package structs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -31,6 +33,30 @@ type ServiceDefinition struct {
|
||||||
Connect *ServiceConnect
|
Connect *ServiceConnect
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *ServiceDefinition) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
type Alias ServiceDefinition
|
||||||
|
|
||||||
|
aux := &struct {
|
||||||
|
EnableTagOverrideSnake bool `json:"enable_tag_override"`
|
||||||
|
TaggedAddressesSnake map[string]ServiceAddress `json:"tagged_addresses"`
|
||||||
|
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(t),
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(data, &aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if aux.EnableTagOverrideSnake {
|
||||||
|
t.EnableTagOverride = aux.EnableTagOverrideSnake
|
||||||
|
}
|
||||||
|
if len(t.TaggedAddresses) == 0 {
|
||||||
|
t.TaggedAddresses = aux.TaggedAddressesSnake
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ServiceDefinition) NodeService() *NodeService {
|
func (s *ServiceDefinition) NodeService() *NodeService {
|
||||||
ns := &NodeService{
|
ns := &NodeService{
|
||||||
Kind: s.Kind,
|
Kind: s.Kind,
|
||||||
|
|
|
@ -99,6 +99,14 @@ const (
|
||||||
// MaxLockDelay provides a maximum LockDelay value for
|
// MaxLockDelay provides a maximum LockDelay value for
|
||||||
// a session. Any value above this will not be respected.
|
// a session. Any value above this will not be respected.
|
||||||
MaxLockDelay = 60 * time.Second
|
MaxLockDelay = 60 * time.Second
|
||||||
|
|
||||||
|
// lockDelayMinThreshold is used in JSON decoding to convert a
|
||||||
|
// numeric lockdelay value from nanoseconds to seconds if it is
|
||||||
|
// below thisthreshold. Users often send a value like 5, which
|
||||||
|
// they assumeis seconds, but because Go uses nanosecond granularity,
|
||||||
|
// ends up being very small. If we see a value below this threshold,
|
||||||
|
// we multiply by time.Second
|
||||||
|
lockDelayMinThreshold = 1000
|
||||||
)
|
)
|
||||||
|
|
||||||
// metaKeyFormat checks if a metadata key string is valid
|
// metaKeyFormat checks if a metadata key string is valid
|
||||||
|
@ -877,6 +885,24 @@ type ServiceConnect struct {
|
||||||
SidecarService *ServiceDefinition `json:",omitempty" bexpr:"-"`
|
SidecarService *ServiceDefinition `json:",omitempty" bexpr:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *ServiceConnect) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
type Alias ServiceConnect
|
||||||
|
aux := &struct {
|
||||||
|
SidecarServiceSnake *ServiceDefinition `json:"sidecar_service"`
|
||||||
|
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(t),
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(data, &aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if t.SidecarService == nil {
|
||||||
|
t.SidecarService = aux.SidecarServiceSnake
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// IsSidecarProxy returns true if the NodeService is a sidecar proxy.
|
// IsSidecarProxy returns true if the NodeService is a sidecar proxy.
|
||||||
func (s *NodeService) IsSidecarProxy() bool {
|
func (s *NodeService) IsSidecarProxy() bool {
|
||||||
return s.Kind == ServiceKindConnectProxy && s.Proxy.DestinationServiceID != ""
|
return s.Kind == ServiceKindConnectProxy && s.Proxy.DestinationServiceID != ""
|
||||||
|
@ -1194,33 +1220,58 @@ func (d *HealthCheckDefinition) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(exported)
|
return json.Marshal(exported)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *HealthCheckDefinition) UnmarshalJSON(data []byte) error {
|
func (t *HealthCheckDefinition) UnmarshalJSON(data []byte) (err error) {
|
||||||
type Alias HealthCheckDefinition
|
type Alias HealthCheckDefinition
|
||||||
aux := &struct {
|
aux := &struct {
|
||||||
Interval string
|
Interval interface{}
|
||||||
Timeout string
|
Timeout interface{}
|
||||||
DeregisterCriticalServiceAfter string
|
DeregisterCriticalServiceAfter interface{}
|
||||||
|
TTL interface{}
|
||||||
*Alias
|
*Alias
|
||||||
}{
|
}{
|
||||||
Alias: (*Alias)(d),
|
Alias: (*Alias)(t),
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(data, &aux); err != nil {
|
if err := json.Unmarshal(data, &aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var err error
|
if aux.Interval != nil {
|
||||||
if aux.Interval != "" {
|
switch v := aux.Interval.(type) {
|
||||||
if d.Interval, err = time.ParseDuration(aux.Interval); err != nil {
|
case string:
|
||||||
return err
|
if t.Interval, err = time.ParseDuration(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
t.Interval = time.Duration(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if aux.Timeout != "" {
|
if aux.Timeout != nil {
|
||||||
if d.Timeout, err = time.ParseDuration(aux.Timeout); err != nil {
|
switch v := aux.Timeout.(type) {
|
||||||
return err
|
case string:
|
||||||
|
if t.Timeout, err = time.ParseDuration(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
t.Timeout = time.Duration(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if aux.DeregisterCriticalServiceAfter != "" {
|
if aux.DeregisterCriticalServiceAfter != nil {
|
||||||
if d.DeregisterCriticalServiceAfter, err = time.ParseDuration(aux.DeregisterCriticalServiceAfter); err != nil {
|
switch v := aux.DeregisterCriticalServiceAfter.(type) {
|
||||||
return err
|
case string:
|
||||||
|
if t.DeregisterCriticalServiceAfter, err = time.ParseDuration(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
t.DeregisterCriticalServiceAfter = time.Duration(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if aux.TTL != nil {
|
||||||
|
switch v := aux.TTL.(type) {
|
||||||
|
case string:
|
||||||
|
if t.TTL, err = time.ParseDuration(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
t.TTL = time.Duration(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -1654,6 +1705,8 @@ const (
|
||||||
SessionTTLMultiplier = 2
|
SessionTTLMultiplier = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Sessions []*Session
|
||||||
|
|
||||||
// Session is used to represent an open session in the KV store.
|
// Session is used to represent an open session in the KV store.
|
||||||
// This issued to associate node checks with acquired locks.
|
// This issued to associate node checks with acquired locks.
|
||||||
type Session struct {
|
type Session struct {
|
||||||
|
@ -1667,7 +1720,36 @@ type Session struct {
|
||||||
|
|
||||||
RaftIndex
|
RaftIndex
|
||||||
}
|
}
|
||||||
type Sessions []*Session
|
|
||||||
|
func (t *Session) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
type Alias Session
|
||||||
|
aux := &struct {
|
||||||
|
LockDelay interface{}
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(t),
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(data, &aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if aux.LockDelay != nil {
|
||||||
|
var dur time.Duration
|
||||||
|
switch v := aux.LockDelay.(type) {
|
||||||
|
case string:
|
||||||
|
if dur, err = time.ParseDuration(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
dur = time.Duration(v)
|
||||||
|
}
|
||||||
|
// Convert low value integers into seconds
|
||||||
|
if dur < lockDelayMinThreshold {
|
||||||
|
dur = dur * time.Second
|
||||||
|
}
|
||||||
|
t.LockDelay = dur
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type SessionOp string
|
type SessionOp string
|
||||||
|
|
||||||
|
|
|
@ -51,40 +51,6 @@ func decodeValue(rawKV interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixupTxnOp looks for non-nil Txn operations and passes them on for
|
|
||||||
// value conversion.
|
|
||||||
func fixupTxnOp(rawOp interface{}) error {
|
|
||||||
rawMap, ok := rawOp.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("unexpected raw op type: %T", rawOp)
|
|
||||||
}
|
|
||||||
for k, v := range rawMap {
|
|
||||||
switch strings.ToLower(k) {
|
|
||||||
case "kv":
|
|
||||||
if v == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return decodeValue(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// fixupTxnOps takes the raw decoded JSON and base64 decodes values in Txn ops,
|
|
||||||
// replacing them with byte arrays.
|
|
||||||
func fixupTxnOps(raw interface{}) error {
|
|
||||||
rawSlice, ok := raw.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("unexpected raw type: %t", raw)
|
|
||||||
}
|
|
||||||
for _, rawOp := range rawSlice {
|
|
||||||
if err := fixupTxnOp(rawOp); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// isWrite returns true if the given operation alters the state store.
|
// isWrite returns true if the given operation alters the state store.
|
||||||
func isWrite(op api.KVOp) bool {
|
func isWrite(op api.KVOp) bool {
|
||||||
switch op {
|
switch op {
|
||||||
|
@ -115,7 +81,7 @@ func (s *HTTPServer) convertOps(resp http.ResponseWriter, req *http.Request) (st
|
||||||
// decode it, we will return a 400 since we don't have enough context to
|
// decode it, we will return a 400 since we don't have enough context to
|
||||||
// associate the error with a given operation.
|
// associate the error with a given operation.
|
||||||
var ops api.TxnOps
|
var ops api.TxnOps
|
||||||
if err := decodeBody(req, &ops, fixupTxnOps); err != nil {
|
if err := decodeBody(req.Body, &ops); err != nil {
|
||||||
resp.WriteHeader(http.StatusBadRequest)
|
resp.WriteHeader(http.StatusBadRequest)
|
||||||
fmt.Fprintf(resp, "Failed to parse body: %v", err)
|
fmt.Fprintf(resp, "Failed to parse body: %v", err)
|
||||||
return nil, 0, false
|
return nil, 0, false
|
||||||
|
|
|
@ -1004,7 +1004,7 @@ func TestAPI_AgentChecks_Docker(t *testing.T) {
|
||||||
t.Fatalf("missing service association for check: %v", check)
|
t.Fatalf("missing service association for check: %v", check)
|
||||||
}
|
}
|
||||||
if check.Type != "docker" {
|
if check.Type != "docker" {
|
||||||
t.Fatalf("expected type ttl, got %s", check.Type)
|
t.Fatalf("expected type docker, got %s", check.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,40 +95,63 @@ func (d *HealthCheckDefinition) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(out)
|
return json.Marshal(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *HealthCheckDefinition) UnmarshalJSON(data []byte) error {
|
func (t *HealthCheckDefinition) UnmarshalJSON(data []byte) (err error) {
|
||||||
type Alias HealthCheckDefinition
|
type Alias HealthCheckDefinition
|
||||||
aux := &struct {
|
aux := &struct {
|
||||||
Interval string
|
IntervalDuration interface{}
|
||||||
Timeout string
|
TimeoutDuration interface{}
|
||||||
DeregisterCriticalServiceAfter string
|
DeregisterCriticalServiceAfterDuration interface{}
|
||||||
*Alias
|
*Alias
|
||||||
}{
|
}{
|
||||||
Alias: (*Alias)(d),
|
Alias: (*Alias)(t),
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(data, &aux); err != nil {
|
if err := json.Unmarshal(data, &aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the values into both the time.Duration and old ReadableDuration fields.
|
// Parse the values into both the time.Duration and old ReadableDuration fields.
|
||||||
var err error
|
|
||||||
if aux.Interval != "" {
|
if aux.IntervalDuration == nil {
|
||||||
if d.IntervalDuration, err = time.ParseDuration(aux.Interval); err != nil {
|
t.IntervalDuration = time.Duration(t.Interval)
|
||||||
return err
|
} else {
|
||||||
|
switch v := aux.IntervalDuration.(type) {
|
||||||
|
case string:
|
||||||
|
if t.IntervalDuration, err = time.ParseDuration(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
t.IntervalDuration = time.Duration(v)
|
||||||
}
|
}
|
||||||
d.Interval = ReadableDuration(d.IntervalDuration)
|
t.Interval = ReadableDuration(t.IntervalDuration)
|
||||||
}
|
}
|
||||||
if aux.Timeout != "" {
|
|
||||||
if d.TimeoutDuration, err = time.ParseDuration(aux.Timeout); err != nil {
|
if aux.TimeoutDuration == nil {
|
||||||
return err
|
t.TimeoutDuration = time.Duration(t.Timeout)
|
||||||
|
} else {
|
||||||
|
switch v := aux.TimeoutDuration.(type) {
|
||||||
|
case string:
|
||||||
|
if t.TimeoutDuration, err = time.ParseDuration(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
t.TimeoutDuration = time.Duration(v)
|
||||||
}
|
}
|
||||||
d.Timeout = ReadableDuration(d.TimeoutDuration)
|
t.Timeout = ReadableDuration(t.TimeoutDuration)
|
||||||
}
|
}
|
||||||
if aux.DeregisterCriticalServiceAfter != "" {
|
if aux.DeregisterCriticalServiceAfterDuration == nil {
|
||||||
if d.DeregisterCriticalServiceAfterDuration, err = time.ParseDuration(aux.DeregisterCriticalServiceAfter); err != nil {
|
t.DeregisterCriticalServiceAfterDuration = time.Duration(t.DeregisterCriticalServiceAfter)
|
||||||
return err
|
} else {
|
||||||
|
switch v := aux.DeregisterCriticalServiceAfterDuration.(type) {
|
||||||
|
case string:
|
||||||
|
if t.DeregisterCriticalServiceAfterDuration, err = time.ParseDuration(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
t.DeregisterCriticalServiceAfterDuration = time.Duration(v)
|
||||||
}
|
}
|
||||||
d.DeregisterCriticalServiceAfter = ReadableDuration(d.DeregisterCriticalServiceAfterDuration)
|
t.DeregisterCriticalServiceAfter = ReadableDuration(t.DeregisterCriticalServiceAfterDuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -134,19 +134,28 @@ func (d *ReadableDuration) MarshalJSON() ([]byte, error) {
|
||||||
return []byte(fmt.Sprintf(`"%s"`, d.Duration().String())), nil
|
return []byte(fmt.Sprintf(`"%s"`, d.Duration().String())), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ReadableDuration) UnmarshalJSON(raw []byte) error {
|
func (d *ReadableDuration) UnmarshalJSON(raw []byte) (err error) {
|
||||||
if d == nil {
|
if d == nil {
|
||||||
return fmt.Errorf("cannot unmarshal to nil pointer")
|
return fmt.Errorf("cannot unmarshal to nil pointer")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dur time.Duration
|
||||||
str := string(raw)
|
str := string(raw)
|
||||||
if len(str) < 2 || str[0] != '"' || str[len(str)-1] != '"' {
|
if len(str) >= 2 && str[0] == '"' && str[len(str)-1] == '"' {
|
||||||
return fmt.Errorf("must be enclosed with quotes: %s", str)
|
// quoted string
|
||||||
}
|
dur, err = time.ParseDuration(str[1 : len(str)-1])
|
||||||
dur, err := time.ParseDuration(str[1 : len(str)-1])
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
|
} else {
|
||||||
|
// no quotes, not a string
|
||||||
|
v, err := strconv.ParseFloat(str, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dur = time.Duration(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
*d = ReadableDuration(dur)
|
*d = ReadableDuration(dur)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue