From 78ad8203a446c3cc079b5215d222883ddb8618bd Mon Sep 17 00:00:00 2001 From: Sarah Adams Date: Tue, 29 Oct 2019 11:13:36 -0700 Subject: [PATCH] Use encoding/json as JSON decoder instead of mapstructure (#6680) Fixes #6147 --- agent/acl_endpoint.go | 63 +-- agent/acl_endpoint_legacy.go | 2 +- agent/agent_endpoint.go | 85 +--- agent/catalog_endpoint.go | 6 +- agent/config_endpoint.go | 2 +- agent/connect_ca_endpoint.go | 2 +- agent/coordinate_endpoint.go | 2 +- agent/discovery_chain_endpoint.go | 60 ++- agent/http.go | 13 +- agent/http_decode_test.go | 650 ++++---------------------- agent/intentions_endpoint.go | 19 +- agent/operator_endpoint.go | 5 +- agent/prepared_query_endpoint.go | 4 +- agent/session_endpoint.go | 60 +-- agent/session_endpoint_test.go | 33 -- agent/structs/acl.go | 64 +++ agent/structs/check_definition.go | 93 ++++ agent/structs/check_type.go | 89 +++- agent/structs/connect_proxy_config.go | 93 +++- agent/structs/intention.go | 21 + agent/structs/service_definition.go | 26 ++ agent/structs/structs.go | 114 ++++- agent/txn_endpoint.go | 36 +- api/agent_test.go | 2 +- api/health.go | 59 ++- api/operator_autopilot.go | 23 +- 26 files changed, 715 insertions(+), 911 deletions(-) diff --git a/agent/acl_endpoint.go b/agent/acl_endpoint.go index 56a111e721..adb3d4cd1a 100644 --- a/agent/acl_endpoint.go +++ b/agent/acl_endpoint.go @@ -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)} } diff --git a/agent/acl_endpoint_legacy.go b/agent/acl_endpoint_legacy.go index e462855d48..be10574834 100644 --- a/agent/acl_endpoint_legacy.go +++ b/agent/acl_endpoint_legacy.go @@ -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 diff --git a/agent/agent_endpoint.go b/agent/agent_endpoint.go index 1124411c2b..12f7141d9e 100644 --- a/agent/agent_endpoint.go +++ b/agent/agent_endpoint.go @@ -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)} } diff --git a/agent/catalog_endpoint.go b/agent/catalog_endpoint.go index f8da6136e6..cc28a6f6ad 100644 --- a/agent/catalog_endpoint.go +++ b/agent/catalog_endpoint.go @@ -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 diff --git a/agent/config_endpoint.go b/agent/config_endpoint.go index 283f9f45e2..925d5bedfe 100644 --- a/agent/config_endpoint.go +++ b/agent/config_endpoint.go @@ -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)} } diff --git a/agent/connect_ca_endpoint.go b/agent/connect_ca_endpoint.go index 402797a8fa..3052f3a23e 100644 --- a/agent/connect_ca_endpoint.go +++ b/agent/connect_ca_endpoint.go @@ -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 diff --git a/agent/coordinate_endpoint.go b/agent/coordinate_endpoint.go index f81f4753f4..40592b947d 100644 --- a/agent/coordinate_endpoint.go +++ b/agent/coordinate_endpoint.go @@ -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 diff --git a/agent/discovery_chain_endpoint.go b/agent/discovery_chain_endpoint.go index 790ff245f8..7e3bd31b71 100644 --- a/agent/discovery_chain_endpoint.go +++ b/agent/discovery_chain_endpoint.go @@ -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 diff --git a/agent/http.go b/agent/http.go index 25bfd626b2..f655ad9e13 100644 --- a/agent/http.go +++ b/agent/http.go @@ -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. diff --git a/agent/http_decode_test.go b/agent/http_decode_test.go index dbfc7e4523..8f0553614d 100644 --- a/agent/http_decode_test.go +++ b/agent/http_decode_test.go @@ -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: // ========================================= diff --git a/agent/intentions_endpoint.go b/agent/intentions_endpoint.go index c042de171a..52f5c0e6b4 100644 --- a/agent/intentions_endpoint.go +++ b/agent/intentions_endpoint.go @@ -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)} } diff --git a/agent/operator_endpoint.go b/agent/operator_endpoint.go index 9be097ab22..fb3c3a7316 100644 --- a/agent/operator_endpoint.go +++ b/agent/operator_endpoint.go @@ -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)} } diff --git a/agent/prepared_query_endpoint.go b/agent/prepared_query_endpoint.go index 318b77c758..8bccb5b936 100644 --- a/agent/prepared_query_endpoint.go +++ b/agent/prepared_query_endpoint.go @@ -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 diff --git a/agent/session_endpoint.go b/agent/session_endpoint.go index 1a6cc930d6..aba7dc1b29 100644 --- a/agent/session_endpoint.go +++ b/agent/session_endpoint.go @@ -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 diff --git a/agent/session_endpoint_test.go b/agent/session_endpoint_test.go index 4e80d8ea30..6485e0254e 100644 --- a/agent/session_endpoint_test.go +++ b/agent/session_endpoint_test.go @@ -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() diff --git a/agent/structs/acl.go b/agent/structs/acl.go index 55f09161d4..60ae09a0c8 100644 --- a/agent/structs/acl.go +++ b/agent/structs/acl.go @@ -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 diff --git a/agent/structs/check_definition.go b/agent/structs/check_definition.go index b955d6f2b9..8a6727f473 100644 --- a/agent/structs/check_definition.go +++ b/agent/structs/check_definition.go @@ -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, diff --git a/agent/structs/check_type.go b/agent/structs/check_type.go index ecfc3f4571..1044104934 100644 --- a/agent/structs/check_type.go +++ b/agent/structs/check_type.go @@ -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 { diff --git a/agent/structs/connect_proxy_config.go b/agent/structs/connect_proxy_config.go index 3921fc0f25..e9bf9f4d7c 100644 --- a/agent/structs/connect_proxy_config.go +++ b/agent/structs/connect_proxy_config.go @@ -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 { diff --git a/agent/structs/intention.go b/agent/structs/intention.go index e928de21cb..8b2247c722 100644 --- a/agent/structs/intention.go +++ b/agent/structs/intention.go @@ -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) diff --git a/agent/structs/service_definition.go b/agent/structs/service_definition.go index 0ba6c6c5eb..7b3643a7e2 100644 --- a/agent/structs/service_definition.go +++ b/agent/structs/service_definition.go @@ -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, diff --git a/agent/structs/structs.go b/agent/structs/structs.go index 833a2940bd..3551cecace 100644 --- a/agent/structs/structs.go +++ b/agent/structs/structs.go @@ -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 diff --git a/agent/txn_endpoint.go b/agent/txn_endpoint.go index 52803ae29c..b90c7af581 100644 --- a/agent/txn_endpoint.go +++ b/agent/txn_endpoint.go @@ -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 diff --git a/api/agent_test.go b/api/agent_test.go index f905807aa3..2d2bb30a0c 100644 --- a/api/agent_test.go +++ b/api/agent_test.go @@ -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) } } diff --git a/api/health.go b/api/health.go index 5908882867..ce8e697505 100644 --- a/api/health.go +++ b/api/health.go @@ -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 } diff --git a/api/operator_autopilot.go b/api/operator_autopilot.go index ec777f7128..0e4ef24649 100644 --- a/api/operator_autopilot.go +++ b/api/operator_autopilot.go @@ -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 }