Use encoding/json as JSON decoder instead of mapstructure (#6680)

Fixes #6147
This commit is contained in:
Sarah Adams 2019-10-29 11:13:36 -07:00 committed by GitHub
parent 2fdfdaec3d
commit 7a4be7863d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 715 additions and 911 deletions

View File

@ -6,7 +6,6 @@ import (
"net/http"
"strconv"
"strings"
"time"
"github.com/hashicorp/consul/acl"
"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)
}
// 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) {
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.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)}
}
@ -521,7 +473,7 @@ func (s *HTTPServer) aclTokenSetInternal(resp http.ResponseWriter, req *http.Req
s.parseToken(req, &args.Token)
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)}
}
@ -567,8 +519,7 @@ func (s *HTTPServer) ACLTokenClone(resp http.ResponseWriter, req *http.Request,
}
s.parseEntMeta(req, &args.ACLToken.EnterpriseMeta)
if err := decodeBody(req, &args.ACLToken, fixTimeAndHashFields); err != nil && err.Error() != "EOF" {
if err := decodeBody(req.Body, &args.ACLToken); err != nil {
return nil, BadRequestError{Reason: fmt.Sprintf("Token decoding failed: %v", err)}
}
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.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)}
}
@ -844,7 +795,7 @@ func (s *HTTPServer) ACLBindingRuleWrite(resp http.ResponseWriter, req *http.Req
s.parseToken(req, &args.Token)
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)}
}
@ -980,7 +931,7 @@ func (s *HTTPServer) ACLAuthMethodWrite(resp http.ResponseWriter, req *http.Requ
s.parseToken(req, &args.Token)
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)}
}
@ -1029,7 +980,7 @@ func (s *HTTPServer) ACLLogin(resp http.ResponseWriter, req *http.Request) (inte
s.parseDC(req, &args.Datacenter)
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)}
}

View File

@ -65,7 +65,7 @@ func (s *HTTPServer) aclSet(resp http.ResponseWriter, req *http.Request, update
// Handle optional request body
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)
fmt.Fprintf(resp, "Request decode failed: %v", err)
return nil, nil

View File

@ -457,11 +457,8 @@ func (s *HTTPServer) syncChanges() {
func (s *HTTPServer) AgentRegisterCheck(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
var args structs.CheckDefinition
// Fixup the type decode of TTL or Interval.
decodeCB := func(raw interface{}) error {
return FixupCheckType(raw)
}
if err := decodeBody(req, &args, decodeCB); err != nil {
if err := decodeBody(req.Body, &args); err != nil {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(resp, "Request decode failed: %v", err)
return nil, nil
@ -606,7 +603,7 @@ type checkUpdate struct {
// APIs.
func (s *HTTPServer) AgentCheckUpdate(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
var update checkUpdate
if err := decodeBody(req, &update, nil); err != nil {
if err := decodeBody(req.Body, &update); err != nil {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(resp, "Request decode failed: %v", err)
return nil, nil
@ -758,7 +755,7 @@ func (s *HTTPServer) AgentRegisterService(resp http.ResponseWriter, req *http.Re
var args structs.ServiceDefinition
// 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)
fmt.Fprintf(resp, "Request decode failed: %v", err)
return nil, nil
@ -894,76 +891,6 @@ func (s *HTTPServer) AgentRegisterService(resp http.ResponseWriter, req *http.Re
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) {
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
// fields to this later if needed.
var args api.AgentToken
if err := decodeBody(req, &args, nil); err != nil {
if err := decodeBody(req.Body, &args); err != nil {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(resp, "Request decode failed: %v", err)
return nil, nil
@ -1339,7 +1266,7 @@ func (s *HTTPServer) AgentConnectAuthorize(resp http.ResponseWriter, req *http.R
// Decode the request from the request body
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)}
}

View File

@ -10,14 +10,12 @@ import (
"github.com/hashicorp/consul/agent/structs"
)
var durations = NewDurationFixer("interval", "timeout", "deregistercriticalserviceafter")
func (s *HTTPServer) CatalogRegister(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
metrics.IncrCounterWithLabels([]string{"client", "api", "catalog_register"}, 1,
[]metrics.Label{{Name: "node", Value: s.nodeName()}})
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)
fmt.Fprintf(resp, "Request decode failed: %v", err)
return nil, nil
@ -46,7 +44,7 @@ func (s *HTTPServer) CatalogDeregister(resp http.ResponseWriter, req *http.Reque
[]metrics.Label{{Name: "node", Value: s.nodeName()}})
var args structs.DeregisterRequest
if err := decodeBody(req, &args, nil); err != nil {
if err := decodeBody(req.Body, &args); err != nil {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(resp, "Request decode failed: %v", err)
return nil, nil

View File

@ -103,7 +103,7 @@ func (s *HTTPServer) ConfigApply(resp http.ResponseWriter, req *http.Request) (i
s.parseToken(req, &args.Token)
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)}
}

View File

@ -62,7 +62,7 @@ func (s *HTTPServer) ConnectCAConfigurationSet(resp http.ResponseWriter, req *ht
var args structs.CARequest
s.parseDC(req, &args.Datacenter)
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)
fmt.Fprintf(resp, "Request decode failed: %v", err)
return nil, nil

View File

@ -150,7 +150,7 @@ func (s *HTTPServer) CoordinateUpdate(resp http.ResponseWriter, req *http.Reques
}
args := structs.CoordinateUpdateRequest{}
if err := decodeBody(req, &args, nil); err != nil {
if err := decodeBody(req.Body, &args); err != nil {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(resp, "Request decode failed: %v", err)
return nil, nil

View File

@ -1,6 +1,7 @@
package agent
import (
"encoding/json"
"fmt"
"net/http"
"strings"
@ -28,7 +29,7 @@ func (s *HTTPServer) DiscoveryChainRead(resp http.ResponseWriter, req *http.Requ
if req.Method == "POST" {
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)}
}
@ -91,6 +92,63 @@ type discoveryChainReadRequest struct {
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
type discoveryChainReadResponse struct {
Chain *structs.CompiledDiscoveryChain

View File

@ -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
}
// decodeBody is used to decode a JSON request body
func decodeBody(req *http.Request, out interface{}, cb func(interface{}) error) error {
func decodeBody(body io.Reader, out interface{}) 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
// 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.

View File

@ -33,8 +33,7 @@ package agent
import (
"bytes"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
@ -348,13 +347,13 @@ var translateScriptArgsTCs = []translateKeyTestCase{
jsonFmtStr: "{" + scriptFields[0] + "," + scriptFields[2] + "}",
equalityFn: scriptArgsEqFn,
},
// {
// desc: "scriptArgs: second and third set",
// in: []interface{}{`["2"]`, `["3"]`},
// want: []string{"2"},
// jsonFmtStr: "{" + scriptFields[1] + "," + scriptFields[2] + "}",
// equalityFn: scriptArgsEqFn,
// },
{
desc: "scriptArgs: second and third set",
in: []interface{}{`["2"]`, `["3"]`},
want: []string{"2"},
jsonFmtStr: "{" + scriptFields[1] + "," + scriptFields[2] + "}",
equalityFn: scriptArgsEqFn,
},
{
desc: "scriptArgs: first set",
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:
// Policy structs.ACLPolicy
// ID string
@ -646,12 +636,15 @@ func TestDecodeACLPolicyWrite(t *testing.T) {
for _, tc := range hashTestCases {
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))
req := httptest.NewRequest("POST", "http://foo.com", body)
var out structs.ACLPolicy
err := decodeBody(req, &out, fixTimeAndHashFields)
err := decodeBody(body, &out)
if err != nil && !tc.wantErr {
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:
// ACLToken structs.ACLToken
// AccessorID string
@ -704,9 +688,7 @@ func TestDecodeACLPolicyWrite(t *testing.T) {
// Datacenter string
// WriteRequest structs.WriteRequest
// Token string
func TestDecodeACLToken(t *testing.T) {
for _, tc := range translateValueTestCases {
t.Run(tc.desc, func(t *testing.T) {
// set up request body
@ -728,13 +710,12 @@ func TestDecodeACLToken(t *testing.T) {
"Hash": %s
}`, expTime, expTTL, createTime, hash))
// set up request
body := bytes.NewBuffer(bodyBytes)
req := httptest.NewRequest("POST", "http://foo.com", body)
// decode body
var out structs.ACLToken
err := decodeBody(req, &out, fixTimeAndHashFields)
err := decodeBody(body, &out)
if err != nil && !tc.wantErr {
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:
// Role structs.ACLRole
// ID string
@ -816,15 +776,17 @@ func TestDecodeACLTokenClone(t *testing.T) {
// Token string
func TestDecodeACLRoleWrite(t *testing.T) {
for _, tc := range hashTestCases {
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))
req := httptest.NewRequest("POST", "http://foo.com", body)
var out structs.ACLRole
err := decodeBody(req, &out, fixTimeAndHashFields)
err := decodeBody(body, &out)
if err == nil && tc.wantErr {
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:
// ID types.CheckID
// Name string
@ -976,16 +833,17 @@ func TestDecodeAgentRegisterCheck(t *testing.T) {
t.Run(tc.desc, func(t *testing.T) {
// set up request body
jsonStr := fmt.Sprintf(`{
"Interval": %[1]s,
"Timeout": %[1]s,
"TTL": %[1]s,
"DeregisterCriticalServiceAfter": %[1]s
}`, tc.durations.in)
body := bytes.NewBuffer([]byte(jsonStr))
req := httptest.NewRequest("POST", "http://foo.com", body)
var out structs.CheckDefinition
err := decodeBody(req, &out, FixupCheckType)
err := decodeBody(body, &out)
if err == nil && tc.wantErr {
t.Fatal("expected err, got nil")
}
@ -998,19 +856,17 @@ func TestDecodeAgentRegisterCheck(t *testing.T) {
}
})
}
// decodeCB:
// - Header field
// - translate keys
for _, tc := range checkTypeHeaderTestCases {
t.Run(tc.desc, func(t *testing.T) {
// set up request body
jsonStr := fmt.Sprintf(`{"Header": %s}`, tc.in)
body := bytes.NewBuffer([]byte(jsonStr))
req := httptest.NewRequest("POST", "http://foo.com", body)
var out structs.CheckDefinition
err := decodeBody(req, &out, FixupCheckType)
err := decodeBody(body, &out)
if err == nil && tc.wantErr {
t.Fatal("expected err, got nil")
}
@ -1027,11 +883,12 @@ func TestDecodeAgentRegisterCheck(t *testing.T) {
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
jsonStr := fmt.Sprintf(tc.jsonFmtStr, tc.in...)
body := bytes.NewBuffer([]byte(jsonStr))
req := httptest.NewRequest("POST", "http://foo.com", body)
var out structs.CheckDefinition
err := decodeBody(req, &out, FixupCheckType)
err := decodeBody(body, &out)
if err != nil {
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:
// Kind structs.ServiceKind
// ID string
@ -1151,8 +972,6 @@ func TestDecodeAgentCheckUpdate(t *testing.T) {
// Native bool
// SidecarService *structs.ServiceDefinition
func TestDecodeAgentRegisterService(t *testing.T) {
var callback = registerServiceDecodeCB
// key translation tests:
// decodeCB fields:
// --------------------
@ -1325,14 +1144,15 @@ func TestDecodeAgentRegisterService(t *testing.T) {
desc: "DestinationNamespace: both set",
in: []interface{}{`"a"`, `"b"`},
want: "a",
jsonFmtStr: `{"Proxy": {"Upstreams": [{` + destinationNamespaceFields[1] + `}]}}`,
jsonFmtStr: `{"Proxy": {"Upstreams": [{` + strings.Join(destinationNamespaceFields, ",") + `}]}}`,
equalityFn: destinationNamespaceEqFn,
},
{
desc: "DestinationNamespace: first set",
in: []interface{}{`"a"`},
want: "a",
jsonFmtStr: `{"Proxy": {"Upstreams": [{` + destinationNamespaceFields[1] + `}]}}`,
jsonFmtStr: `{"Proxy": {"Upstreams": [{` + destinationNamespaceFields[0] + `}]}}`,
equalityFn: destinationNamespaceEqFn,
},
{
@ -1843,10 +1663,10 @@ func TestDecodeAgentRegisterService(t *testing.T) {
t.Run(tc.desc, func(t *testing.T) {
checkJSONStr := fmt.Sprintf(tc.jsonFmtStr, tc.in...)
body := bytes.NewBuffer([]byte(checkJSONStr))
req := httptest.NewRequest("POST", "http://foo.com", body)
var out structs.ServiceDefinition
err := decodeBody(req, &out, callback)
err := decodeBody(body, &out)
if err != nil {
t.Fatal(err)
}
@ -1864,26 +1684,26 @@ func TestDecodeAgentRegisterService(t *testing.T) {
t.Run(tc.desc, func(t *testing.T) {
// set up request body
jsonStr := fmt.Sprintf(`{
"Check": {
"Interval": %[1]s,
"Timeout": %[1]s,
"TTL": %[1]s,
"DeregisterCriticalServiceAfter": %[1]s
},
"Checks": [
{
"Check": {
"Interval": %[1]s,
"Timeout": %[1]s,
"TTL": %[1]s,
"DeregisterCriticalServiceAfter": %[1]s
}
]
}`, tc.durations.in)
},
"Checks": [
{
"Interval": %[1]s,
"Timeout": %[1]s,
"TTL": %[1]s,
"DeregisterCriticalServiceAfter": %[1]s
}
]
}`, tc.durations.in)
body := bytes.NewBuffer([]byte(jsonStr))
req := httptest.NewRequest("POST", "http://foo.com", body)
var out structs.ServiceDefinition
err := decodeBody(req, &out, callback)
err := decodeBody(body, &out)
if err == nil && tc.wantErr {
t.Fatal("expected err, got nil")
}
@ -1917,10 +1737,10 @@ func TestDecodeAgentRegisterService(t *testing.T) {
}`, checkJSONStr)
body := bytes.NewBuffer([]byte(jsonStr))
req := httptest.NewRequest("POST", "http://foo.com", body)
var out structs.ServiceDefinition
err := decodeBody(req, &out, callback)
err := decodeBody(body, &out)
if err == nil && tc.wantErr {
t.Fatal("expected err, got nil")
}
@ -1951,10 +1771,10 @@ func TestDecodeAgentRegisterService(t *testing.T) {
"Checks": [%[1]s]
}`, checkJSONStr)
body := bytes.NewBuffer([]byte(jsonStr))
req := httptest.NewRequest("POST", "http://foo.com", body)
var out structs.ServiceDefinition
err := decodeBody(req, &out, callback)
err := decodeBody(body, &out)
if err != nil {
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:
// Datacenter string
// ID types.NodeID
@ -2161,16 +1943,19 @@ func TestDecodeCatalogRegister(t *testing.T) {
}
}`, tc.durations.in)
body := bytes.NewBuffer([]byte(jsonStr))
req := httptest.NewRequest("POST", "http://foo.com", body)
var out structs.RegisterRequest
err := decodeBody(req, &out, durations.FixupDurations)
err := decodeBody(body, &out)
if err == nil && tc.wantErr {
t.Fatal("expected err, got nil")
}
if err != nil && !tc.wantErr {
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.
// 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:
// OverrideMeshGateway structs.MeshGatewayConfig
// Mode structs.MeshGatewayMode // string
// OverrideProtocol string
// OverrideConnectTimeout time.Duration
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{
{
desc: "positive string integer (weakly typed)",
@ -2335,11 +2014,9 @@ func TestDecodeDiscoveryChainRead(t *testing.T) {
"OverrideConnectTimeout": %s
}`, tc.durations.in)
body := bytes.NewBuffer([]byte(jsonStr))
req := httptest.NewRequest("POST", "http://foo.com", body)
var out discoveryChainReadRequest
// fullDecodeFn is declared above in this test.
err := fullDecodeFn(req, &out)
err := decodeBody(body, &out)
if err == nil && tc.wantErr {
t.Fatal("expected err, got nil")
}
@ -2371,7 +2048,7 @@ func TestDecodeDiscoveryChainRead(t *testing.T) {
{
desc: "bool for string field (weakly typed)",
in: `true`,
want: "1",
want: "true", // previously: "1"
},
{
desc: "float for string field (weakly typed)",
@ -2384,9 +2061,9 @@ func TestDecodeDiscoveryChainRead(t *testing.T) {
wantErr: true,
},
{
desc: "slice for string field (weakly typed)",
in: `[]`,
want: "",
desc: "slice for string field (weakly typed)",
in: `[]`,
wantErr: true, // previously: want: ""
},
}
@ -2394,15 +2071,14 @@ func TestDecodeDiscoveryChainRead(t *testing.T) {
t.Run(tc.desc, func(t *testing.T) {
// set up request body
jsonStr := fmt.Sprintf(`{
"OverrideProtocol": %[1]s,
"OverrideMeshGateway": {"Mode": %[1]s}
}`, tc.in)
"OverrideProtocol": %[1]s,
"OverrideMeshGateway": {"Mode": %[1]s}
}`, tc.in)
body := bytes.NewBuffer([]byte(jsonStr))
req := httptest.NewRequest("POST", "http://foo.com", body)
var out discoveryChainReadRequest
// fullDecodeFn is declared above in this test.
err := fullDecodeFn(req, &out)
err := decodeBody(body, &out)
if err == nil && tc.wantErr {
t.Fatal("expected err, got nil")
}
@ -2552,8 +2228,6 @@ func TestDecodeDiscoveryChainRead(t *testing.T) {
},
}
// from decodeDiscoveryChainReadRequest:
//
// lib.TranslateKeys(raw, map[string]string{
// "override_mesh_gateway": "overridemeshgateway",
// "override_protocol": "overrideprotocol",
@ -2571,11 +2245,9 @@ func TestDecodeDiscoveryChainRead(t *testing.T) {
t.Run(tc.desc, func(t *testing.T) {
jsonStr := fmt.Sprintf(tc.jsonFmtStr, tc.in...)
body := bytes.NewBuffer([]byte(jsonStr))
req := httptest.NewRequest("POST", "http://foo.com", body)
var out discoveryChainReadRequest
// fullDecodeFn is declared above in this test.
err := fullDecodeFn(req, &out)
err := decodeBody(body, &out)
if err != nil {
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:
// Datacenter string
// Op structs.IntentionOp
@ -2639,13 +2303,12 @@ func TestDecodeIntentionCreate(t *testing.T) {
"Hash": %s
}`, createdAt, updatedAt, hash))
// set up request
body := bytes.NewBuffer(bodyBytes)
req := httptest.NewRequest("POST", "http://foo.com", body)
// decode body
var out structs.Intention
err := decodeBody(req, &out, fixHashField)
err := decodeBody(body, &out)
if tc.hashes != nil {
// 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:
// CleanupDeadServers bool
// LastContactThreshold *api.ReadableDuration
@ -2745,10 +2370,10 @@ func TestDecodeOperatorAutopilotConfiguration(t *testing.T) {
}`, tc.durations.in)
body := bytes.NewBuffer([]byte(jsonStr))
req := httptest.NewRequest("POST", "http://foo.com", body)
var out api.AutopilotConfiguration
err := decodeBody(req, &out, durations.FixupDurations)
err := decodeBody(body, &out)
if err == nil && tc.wantErr {
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:
// Datacenter string
// Op structs.SessionOp
@ -2854,22 +2416,10 @@ func TestDecodePreparedQueryGeneral_Update(t *testing.T) {
// WriteRequest structs.WriteRequest
// Token string
func TestDecodeSessionCreate(t *testing.T) {
// outSession var is shared among test cases b/c of the
// nature/signature of the FixupChecks callback.
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
sessionDurationTCs := append(positiveDurationTCs,
@ -2901,25 +2451,6 @@ func TestDecodeSessionCreate(t *testing.T) {
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 {
@ -2935,10 +2466,10 @@ func TestDecodeSessionCreate(t *testing.T) {
}`, tc.durations.in)
body := bytes.NewBuffer([]byte(jsonStr))
req := httptest.NewRequest("POST", "http://foo.com", body)
// outSession var is shared among test cases
err := decodeBody(req, &outSession, fixupCB)
err := decodeBody(body, &outSession)
if err == nil && tc.wantErr {
t.Fatal("expected err, got nil")
}
@ -3002,10 +2533,8 @@ func TestDecodeSessionCreate(t *testing.T) {
}`, tc.in)
body := bytes.NewBuffer([]byte(jsonStr))
req := httptest.NewRequest("POST", "http://foo.com", body)
// outSession var is shared among test cases
err := decodeBody(req, &outSession, fixupCB)
err := decodeBody(body, &outSession)
if err == nil && tc.wantErr {
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:
// KV *api.KVTxnOp
// Verb api.KVOp
@ -3181,10 +2701,10 @@ func TestDecodeTxnConvertOps(t *testing.T) {
}]`, tc.durations.in)
body := bytes.NewBuffer([]byte(jsonStr))
req := httptest.NewRequest("POST", "http://foo.com", body)
var out api.TxnOps
err := decodeBody(req, &out, fixupTxnOps)
err := decodeBody(body, &out)
if err == nil && tc.wantErr {
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:
// =========================================

View File

@ -9,21 +9,6 @@ import (
"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
func (s *HTTPServer) IntentionEndpoint(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
switch req.Method {
@ -65,7 +50,7 @@ func (s *HTTPServer) IntentionCreate(resp http.ResponseWriter, req *http.Request
}
s.parseDC(req, &args.Datacenter)
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)
}
@ -258,7 +243,7 @@ func (s *HTTPServer) IntentionSpecificUpdate(id string, resp http.ResponseWriter
}
s.parseDC(req, &args.Datacenter)
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)}
}

View File

@ -76,7 +76,7 @@ type keyringArgs struct {
func (s *HTTPServer) OperatorKeyringEndpoint(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
var args keyringArgs
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)}
}
}
@ -218,8 +218,7 @@ func (s *HTTPServer) OperatorAutopilotConfiguration(resp http.ResponseWriter, re
s.parseToken(req, &args.Token)
var conf api.AutopilotConfiguration
durations := NewDurationFixer("lastcontactthreshold", "serverstabilizationtime")
if err := decodeBody(req, &conf, durations.FixupDurations); err != nil {
if err := decodeBody(req.Body, &conf); err != nil {
return nil, BadRequestError{Reason: fmt.Sprintf("Error parsing autopilot config: %v", err)}
}

View File

@ -23,7 +23,7 @@ func (s *HTTPServer) preparedQueryCreate(resp http.ResponseWriter, req *http.Req
}
s.parseDC(req, &args.Datacenter)
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)
fmt.Fprintf(resp, "Request decode failed: %v", err)
return nil, nil
@ -253,7 +253,7 @@ func (s *HTTPServer) preparedQueryUpdate(id string, resp http.ResponseWriter, re
s.parseDC(req, &args.Datacenter)
s.parseToken(req, &args.Token)
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)
fmt.Fprintf(resp, "Request decode failed: %v", err)
return nil, nil

View File

@ -10,16 +10,6 @@ import (
"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
type sessionCreateResponse struct {
ID string
@ -44,16 +34,7 @@ func (s *HTTPServer) SessionCreate(resp http.ResponseWriter, req *http.Request)
// Handle optional request body
if req.ContentLength > 0 {
fixup := func(raw interface{}) error {
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 {
if err := decodeBody(req.Body, &args.Session); err != nil {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(resp, "Request decode failed: %v", err)
return nil, nil
@ -70,45 +51,6 @@ func (s *HTTPServer) SessionCreate(resp http.ResponseWriter, req *http.Request)
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
// 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

View File

@ -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 {
req, _ := http.NewRequest("PUT", "/v1/session/create", nil)
resp := httptest.NewRecorder()

View File

@ -2,6 +2,7 @@ package structs
import (
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"hash"
@ -260,6 +261,35 @@ type ACLToken struct {
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 {
t2 := *t
t2.Policies = nil
@ -539,6 +569,23 @@ type ACLPolicy struct {
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 {
p2 := *p
p2.Datacenters = cloneStringSlice(p.Datacenters)
@ -768,6 +815,23 @@ type ACLRole struct {
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 {
r2 := *r
r2.Policies = nil

View File

@ -1,6 +1,7 @@
package structs
import (
"encoding/json"
"time"
"github.com/hashicorp/consul/api"
@ -42,6 +43,98 @@ type CheckDefinition struct {
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 {
health := &HealthCheck{
Node: node,

View File

@ -1,6 +1,7 @@
package structs
import (
"encoding/json"
"fmt"
"reflect"
"time"
@ -8,6 +9,8 @@ import (
"github.com/hashicorp/consul/types"
)
type CheckTypes []*CheckType
// CheckType is used to create either the CheckMonitor or the CheckTTL.
// 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
@ -55,7 +58,91 @@ type CheckType struct {
DeregisterCriticalServiceAfter time.Duration
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
func (c *CheckType) Validate() error {

View File

@ -3,9 +3,10 @@ package structs
import (
"encoding/json"
"fmt"
"log"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/lib"
"log"
)
const (
@ -120,6 +121,38 @@ type ConnectProxyConfig struct {
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) {
type typeCopy ConnectProxyConfig
copy := typeCopy(*c)
@ -217,6 +250,41 @@ type Upstream struct {
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
func (u *Upstream) Validate() error {
switch u.DestinationType {
@ -352,6 +420,29 @@ type ExposePath struct {
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 {
paths := make([]api.ExposePath, 0)
for _, p := range e.Paths {

View File

@ -2,6 +2,7 @@ package structs
import (
"encoding/binary"
"encoding/json"
"fmt"
"sort"
"strconv"
@ -84,6 +85,26 @@ type Intention struct {
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 {
if force || x.Hash == nil {
hash, err := blake2b.New256(nil)

View File

@ -1,6 +1,8 @@
package structs
import (
"encoding/json"
"github.com/hashicorp/go-multierror"
)
@ -31,6 +33,30 @@ type ServiceDefinition struct {
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 {
ns := &NodeService{
Kind: s.Kind,

View File

@ -99,6 +99,14 @@ const (
// MaxLockDelay provides a maximum LockDelay value for
// a session. Any value above this will not be respected.
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
@ -877,6 +885,24 @@ type ServiceConnect struct {
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.
func (s *NodeService) IsSidecarProxy() bool {
return s.Kind == ServiceKindConnectProxy && s.Proxy.DestinationServiceID != ""
@ -1194,33 +1220,58 @@ func (d *HealthCheckDefinition) MarshalJSON() ([]byte, error) {
return json.Marshal(exported)
}
func (d *HealthCheckDefinition) UnmarshalJSON(data []byte) error {
func (t *HealthCheckDefinition) UnmarshalJSON(data []byte) (err error) {
type Alias HealthCheckDefinition
aux := &struct {
Interval string
Timeout string
DeregisterCriticalServiceAfter string
Interval interface{}
Timeout interface{}
DeregisterCriticalServiceAfter interface{}
TTL interface{}
*Alias
}{
Alias: (*Alias)(d),
Alias: (*Alias)(t),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
var err error
if aux.Interval != "" {
if d.Interval, err = time.ParseDuration(aux.Interval); err != nil {
return err
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 != "" {
if d.Timeout, err = time.ParseDuration(aux.Timeout); err != nil {
return err
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.DeregisterCriticalServiceAfter != "" {
if d.DeregisterCriticalServiceAfter, err = time.ParseDuration(aux.DeregisterCriticalServiceAfter); err != nil {
return err
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)
}
}
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
@ -1654,6 +1705,8 @@ const (
SessionTTLMultiplier = 2
)
type Sessions []*Session
// Session is used to represent an open session in the KV store.
// This issued to associate node checks with acquired locks.
type Session struct {
@ -1667,7 +1720,36 @@ type Session struct {
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

View File

@ -51,40 +51,6 @@ func decodeValue(rawKV interface{}) error {
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.
func isWrite(op api.KVOp) bool {
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
// associate the error with a given operation.
var ops api.TxnOps
if err := decodeBody(req, &ops, fixupTxnOps); err != nil {
if err := decodeBody(req.Body, &ops); err != nil {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(resp, "Failed to parse body: %v", err)
return nil, 0, false

View File

@ -1004,7 +1004,7 @@ func TestAPI_AgentChecks_Docker(t *testing.T) {
t.Fatalf("missing service association for check: %v", check)
}
if check.Type != "docker" {
t.Fatalf("expected type ttl, got %s", check.Type)
t.Fatalf("expected type docker, got %s", check.Type)
}
}

View File

@ -95,40 +95,63 @@ func (d *HealthCheckDefinition) MarshalJSON() ([]byte, error) {
return json.Marshal(out)
}
func (d *HealthCheckDefinition) UnmarshalJSON(data []byte) error {
func (t *HealthCheckDefinition) UnmarshalJSON(data []byte) (err error) {
type Alias HealthCheckDefinition
aux := &struct {
Interval string
Timeout string
DeregisterCriticalServiceAfter string
IntervalDuration interface{}
TimeoutDuration interface{}
DeregisterCriticalServiceAfterDuration interface{}
*Alias
}{
Alias: (*Alias)(d),
Alias: (*Alias)(t),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
// Parse the values into both the time.Duration and old ReadableDuration fields.
var err error
if aux.Interval != "" {
if d.IntervalDuration, err = time.ParseDuration(aux.Interval); err != nil {
return err
if aux.IntervalDuration == nil {
t.IntervalDuration = time.Duration(t.Interval)
} 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 {
return err
if aux.TimeoutDuration == nil {
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 d.DeregisterCriticalServiceAfterDuration, err = time.ParseDuration(aux.DeregisterCriticalServiceAfter); err != nil {
return err
if aux.DeregisterCriticalServiceAfterDuration == nil {
t.DeregisterCriticalServiceAfterDuration = time.Duration(t.DeregisterCriticalServiceAfter)
} 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
}

View File

@ -134,19 +134,28 @@ func (d *ReadableDuration) MarshalJSON() ([]byte, error) {
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 {
return fmt.Errorf("cannot unmarshal to nil pointer")
}
var dur time.Duration
str := string(raw)
if len(str) < 2 || str[0] != '"' || str[len(str)-1] != '"' {
return fmt.Errorf("must be enclosed with quotes: %s", str)
}
dur, err := time.ParseDuration(str[1 : len(str)-1])
if err != nil {
return err
if len(str) >= 2 && str[0] == '"' && str[len(str)-1] == '"' {
// quoted string
dur, err = time.ParseDuration(str[1 : len(str)-1])
if err != nil {
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)
return nil
}