Unverified Commit c7e192a0 authored by Scarlett Perry's avatar Scarlett Perry Committed by GitHub
Browse files

Revert "Revert "audit: record request/response data in event (#543)"" (#590)

parent 4ad79857
Showing with 1267 additions and 310 deletions
+1267 -310
......@@ -5,6 +5,7 @@ package clutch.audit.v1;
option go_package = "github.com/lyft/clutch/backend/api/audit/v1;auditv1";
import "google/api/annotations.proto";
import "google/protobuf/any.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/timestamp.proto";
import "google/rpc/status.proto";
......@@ -45,6 +46,14 @@ message Resource {
string id = 2;
}
message RequestMetadata {
google.protobuf.Any body = 1;
}
message ResponseMetadata {
google.protobuf.Any body = 1;
}
message RequestEvent {
option (clutch.api.v1.reference).fields = "resources";
......@@ -65,6 +74,12 @@ message RequestEvent {
// The resources touched during the event.
repeated Resource resources = 6;
// The API request saved as metadata for the event.
RequestMetadata request_metadata = 7;
// The API response saved as metadata for the event.
ResponseMetadata response_metadata = 8;
}
message Event {
......
This diff is collapsed.
......@@ -282,6 +282,156 @@ var _ interface {
ErrorName() string
} = ResourceValidationError{}
// Validate checks the field values on RequestMetadata with the rules defined
// in the proto definition for this message. If any rules are violated, an
// error is returned.
func (m *RequestMetadata) Validate() error {
if m == nil {
return nil
}
if v, ok := interface{}(m.GetBody()).(interface{ Validate() error }); ok {
if err := v.Validate(); err != nil {
return RequestMetadataValidationError{
field: "Body",
reason: "embedded message failed validation",
cause: err,
}
}
}
return nil
}
// RequestMetadataValidationError is the validation error returned by
// RequestMetadata.Validate if the designated constraints aren't met.
type RequestMetadataValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e RequestMetadataValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e RequestMetadataValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e RequestMetadataValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e RequestMetadataValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e RequestMetadataValidationError) ErrorName() string { return "RequestMetadataValidationError" }
// Error satisfies the builtin error interface
func (e RequestMetadataValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sRequestMetadata.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = RequestMetadataValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = RequestMetadataValidationError{}
// Validate checks the field values on ResponseMetadata with the rules defined
// in the proto definition for this message. If any rules are violated, an
// error is returned.
func (m *ResponseMetadata) Validate() error {
if m == nil {
return nil
}
if v, ok := interface{}(m.GetBody()).(interface{ Validate() error }); ok {
if err := v.Validate(); err != nil {
return ResponseMetadataValidationError{
field: "Body",
reason: "embedded message failed validation",
cause: err,
}
}
}
return nil
}
// ResponseMetadataValidationError is the validation error returned by
// ResponseMetadata.Validate if the designated constraints aren't met.
type ResponseMetadataValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e ResponseMetadataValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e ResponseMetadataValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e ResponseMetadataValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e ResponseMetadataValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e ResponseMetadataValidationError) ErrorName() string { return "ResponseMetadataValidationError" }
// Error satisfies the builtin error interface
func (e ResponseMetadataValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sResponseMetadata.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = ResponseMetadataValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = ResponseMetadataValidationError{}
// Validate checks the field values on RequestEvent with the rules defined in
// the proto definition for this message. If any rules are violated, an error
// is returned.
......@@ -343,6 +493,26 @@ func (m *RequestEvent) Validate() error {
}
if v, ok := interface{}(m.GetRequestMetadata()).(interface{ Validate() error }); ok {
if err := v.Validate(); err != nil {
return RequestEventValidationError{
field: "RequestMetadata",
reason: "embedded message failed validation",
cause: err,
}
}
}
if v, ok := interface{}(m.GetResponseMetadata()).(interface{ Validate() error }); ok {
if err := v.Validate(); err != nil {
return RequestEventValidationError{
field: "ResponseMetadata",
reason: "embedded message failed validation",
cause: err,
}
}
}
return nil
}
......
......@@ -10,6 +10,7 @@ import (
"google.golang.org/grpc"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/known/anypb"
apiv1 "github.com/lyft/clutch/backend/api/api/v1"
auditv1 "github.com/lyft/clutch/backend/api/audit/v1"
......@@ -135,3 +136,14 @@ func resolvePattern(pb proto.Message, pattern *apiv1.Pattern) *auditv1.Resource
}
return &auditv1.Resource{TypeUrl: pattern.TypeUrl, Id: resourceName}
}
// APIBody returns a API request/response interface as an anypb.Any message.
func APIBody(body interface{}) (*anypb.Any, error) {
m, ok := body.(proto.Message)
if !ok {
// body is not the type/value we want to process
return nil, nil
}
return anypb.New(m)
}
......@@ -11,6 +11,7 @@ import (
auditv1 "github.com/lyft/clutch/backend/api/audit/v1"
ec2v1 "github.com/lyft/clutch/backend/api/aws/ec2/v1"
healthcheckv1 "github.com/lyft/clutch/backend/api/healthcheck/v1"
k8sapiv1 "github.com/lyft/clutch/backend/api/k8s/v1"
"github.com/lyft/clutch/backend/module"
"github.com/lyft/clutch/backend/module/healthcheck"
)
......@@ -110,3 +111,38 @@ func TestResourceNames(t *testing.T) {
})
}
}
func TestAPIBody(t *testing.T) {
// proto.message with nil value
m := (*ec2v1.Instance)(nil)
var tests = []struct {
input interface{}
expectNil bool
}{
// case: type is proto.message
{input: &ec2v1.Instance{InstanceId: "i-123456789abcdef0"}, expectNil: false},
// case: type is proto.message
{input: &k8sapiv1.ResizeHPAResponse{}, expectNil: false},
// case: type is proto.message
// anypb.New gracefully hanldes (*myProtoStruct)(nil)
{input: m, expectNil: false},
// case: type is struct
{input: ec2v1.Instance{InstanceId: "i-123456789abcdef0"}, expectNil: true},
// case: untyped nil
{input: nil, expectNil: true},
// case: type is string
{input: "foo", expectNil: true},
}
for _, test := range tests {
result, err := APIBody(test.input)
if test.expectNil {
assert.Nil(t, result)
} else {
assert.NotNil(t, result)
}
assert.NoError(t, err)
}
}
......@@ -52,7 +52,11 @@ type auditEntryContextKey struct{}
func (m *mid) UnaryInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
event := m.eventFromRequest(ctx, req, info)
event, err := m.eventFromRequest(ctx, req, info)
if err != nil {
return nil, err
}
id, err := m.audit.WriteRequestEvent(ctx, event)
if err != nil && !errors.Is(err, auditservice.ErrFailedFilters) {
return nil, fmt.Errorf("could not make call %s because failed to audit: %w", info.FullMethod, err)
......@@ -61,8 +65,13 @@ func (m *mid) UnaryInterceptor() grpc.UnaryServerInterceptor {
ctx = context.WithValue(ctx, auditEntryContextKey{}, id)
resp, err := handler(ctx, req)
// TODO (sperry): move the response recording into a goroutine so it's async
if id != -1 {
update := m.eventFromResponse(resp, err)
update, err := m.eventFromResponse(resp, err)
if err != nil {
return nil, err
}
if auditErr := m.audit.UpdateRequestEvent(ctx, id, update); auditErr != nil {
m.logger.Warn("error updating audit event",
zap.Int64("auditID", id),
......@@ -75,7 +84,7 @@ func (m *mid) UnaryInterceptor() grpc.UnaryServerInterceptor {
}
}
func (m *mid) eventFromRequest(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo) *auditv1.RequestEvent {
func (m *mid) eventFromRequest(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo) (*auditv1.RequestEvent, error) {
svc, method, ok := middleware.SplitFullMethod(info.FullMethod)
if !ok {
m.logger.Warn("could not parse gRPC method", zap.String("fullMethod", info.FullMethod))
......@@ -86,23 +95,51 @@ func (m *mid) eventFromRequest(ctx context.Context, req interface{}, info *grpc.
username = claims.Subject
}
reqBody, err := meta.APIBody(req)
if err != nil {
return nil, err
}
return &auditv1.RequestEvent{
Username: username,
ServiceName: svc,
MethodName: method,
Type: meta.GetAction(info.FullMethod),
Resources: meta.ResourceNames(req.(proto.Message)),
}
RequestMetadata: &auditv1.RequestMetadata{
Body: reqBody,
},
}, nil
}
func (m *mid) eventFromResponse(resp interface{}, err error) *auditv1.RequestEvent {
func (m *mid) eventFromResponse(resp interface{}, err error) (*auditv1.RequestEvent, error) {
s := status.Convert(err)
if s == nil {
s = status.New(codes.OK, "")
}
// if err is returned from handler, discard the primary value for the resp
// and return an empty list for resources and a nil Any message for respBody
if err != nil {
return &auditv1.RequestEvent{
Status: s.Proto(),
Resources: nil,
ResponseMetadata: &auditv1.ResponseMetadata{
Body: nil,
},
}, nil
}
respBody, err := meta.APIBody(resp)
if err != nil {
return nil, err
}
return &auditv1.RequestEvent{
Status: s.Proto(),
Resources: meta.ResourceNames(resp.(proto.Message)),
}
ResponseMetadata: &auditv1.ResponseMetadata{
Body: respBody,
},
}, nil
}
package audit
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/anypb"
k8sapiv1 "github.com/lyft/clutch/backend/api/k8s/v1"
)
func TestEventFromResponse(t *testing.T) {
m := &mid{}
err := errors.New("error")
a := (*anypb.Any)(nil)
// case: err, passed to eventFromResponse, does not equal nil
event, err := m.eventFromResponse(nil, err)
assert.NoError(t, err)
assert.NotEmpty(t, event)
assert.Equal(t, "error", event.Status.Message)
assert.Equal(t, 0, len(event.Resources))
assert.Equal(t, a, event.ResponseMetadata.Body)
resp := &k8sapiv1.Pod{Cluster: "kind-clutch", Namespace: "envoy-staging", Name: "envoy-main-579848cc64-cxnqm"}
// case: err, passed to eventFromResponse, equals nil
event, err = m.eventFromResponse(resp, nil)
assert.NoError(t, err)
assert.NotEmpty(t, event)
assert.Equal(t, int32(0), event.Status.Code)
assert.Equal(t, "", event.Status.Message)
assert.Equal(t, 1, len(event.Resources))
assert.Equal(t, "type.googleapis.com/clutch.k8s.v1.Pod", event.ResponseMetadata.Body.TypeUrl)
}
......@@ -12,6 +12,8 @@ import (
"github.com/uber-go/tally"
"go.uber.org/zap"
rpcstatus "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/known/anypb"
apiv1 "github.com/lyft/clutch/backend/api/api/v1"
auditv1 "github.com/lyft/clutch/backend/api/audit/v1"
......@@ -54,12 +56,18 @@ func (c *client) WriteRequestEvent(ctx context.Context, event *auditv1.RequestEv
return -1, errors.New("cannot write empty event to table")
}
reqBody, err := convertAPIBody(event.RequestMetadata.Body)
if err != nil {
return -1, err
}
dbEvent := &eventDetails{
Username: event.Username,
Service: event.ServiceName,
Method: event.MethodName,
ActionType: event.Type.String(),
RequestResources: convertResources(event.Resources),
RequestBody: reqBody,
}
blob, err := json.Marshal(dbEvent)
if err != nil {
......@@ -77,12 +85,18 @@ func (c *client) WriteRequestEvent(ctx context.Context, event *auditv1.RequestEv
}
func (c *client) UpdateRequestEvent(ctx context.Context, id int64, update *auditv1.RequestEvent) error {
respBody, err := convertAPIBody(update.ResponseMetadata.Body)
if err != nil {
return err
}
dbEvent := &eventDetails{
Status: status{
Code: int(update.Status.Code),
Message: update.Status.Message,
},
ResponseResources: convertResources(update.Resources),
ResponseBody: respBody,
}
blob, err := json.Marshal(dbEvent)
if err != nil {
......@@ -164,7 +178,7 @@ func (c *client) query(ctx context.Context, query string, args ...interface{}) (
proto := &auditv1.Event{
OccurredAt: occurred,
EventType: &auditv1.Event_Event{
Event: row.RequestEventProto(),
Event: requestEventProto(c.logger, row),
},
}
events = append(events, proto)
......@@ -191,13 +205,15 @@ type resource struct {
}
type eventDetails struct {
Username string `json:"user_name,omitempty"`
Service string `json:"service_name,omitempty"`
Method string `json:"method_name,omitempty"`
ActionType string `json:"type,omitempty"`
Status status `json:"status,omitempty"`
RequestResources []*resource `json:"request_resources,omitempty"`
ResponseResources []*resource `json:"response_resources,omitempty"`
Username string `json:"user_name,omitempty"`
Service string `json:"service_name,omitempty"`
Method string `json:"method_name,omitempty"`
ActionType string `json:"type,omitempty"`
Status status `json:"status,omitempty"`
RequestResources []*resource `json:"request_resources,omitempty"`
ResponseResources []*resource `json:"response_resources,omitempty"`
RequestBody json.RawMessage `json:"request_body,omitempty"`
ResponseBody json.RawMessage `json:"response_body,omitempty"`
}
func (e *eventDetails) ResourcesProto() []*auditv1.Resource {
......@@ -228,7 +244,17 @@ type event struct {
Details *eventDetails
}
func (e *event) RequestEventProto() *auditv1.RequestEvent {
func requestEventProto(logger *zap.Logger, e *event) *auditv1.RequestEvent {
reqBody, err := apiBodyProto(e.Details.RequestBody)
if err != nil {
logger.Error("unmarshallable object for RequestBody", zap.Error(err))
}
respBody, err := apiBodyProto(e.Details.ResponseBody)
if err != nil {
logger.Error("unmarshallable object for ResponseBody", zap.Error(err))
}
return &auditv1.RequestEvent{
Username: e.Details.Username,
ServiceName: e.Details.Service,
......@@ -236,6 +262,12 @@ func (e *event) RequestEventProto() *auditv1.RequestEvent {
Type: apiv1.ActionType(apiv1.ActionType_value[e.Details.ActionType]),
Status: e.Details.Status.Status(),
Resources: e.Details.ResourcesProto(),
RequestMetadata: &auditv1.RequestMetadata{
Body: reqBody,
},
ResponseMetadata: &auditv1.ResponseMetadata{
Body: respBody,
},
}
}
......@@ -246,3 +278,31 @@ func convertResources(proto []*auditv1.Resource) []*resource {
}
return resources
}
// Encodes proto object in JSON format
func convertAPIBody(body *anypb.Any) (json.RawMessage, error) {
// gracefully handles untyped and typed nils
// returns empty object if input is untyped or type nil
b, err := protojson.Marshal(body)
if err != nil {
return nil, err
}
return json.RawMessage(b), nil
}
// apiBodyProto reads the given []byte into the Any proto msg
func apiBodyProto(details json.RawMessage) (*anypb.Any, error) {
// protojson.Marshal does not gracefully handle nil values
if details == nil {
return nil, nil
}
body := &anypb.Any{}
err := protojson.Unmarshal(details, body)
if err != nil {
return nil, err
}
return body, nil
}
package sql
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/anypb"
ec2v1 "github.com/lyft/clutch/backend/api/aws/ec2/v1"
k8sapiv1 "github.com/lyft/clutch/backend/api/k8s/v1"
)
func TestConvertAPIBody(t *testing.T) {
// set up for TestConvertAPIBody
a1 := (*anypb.Any)(nil)
p1 := &ec2v1.Instance{InstanceId: "i-123456789abcdef0"}
a2, _ := anypb.New(p1)
p2 := &k8sapiv1.ResizeHPAResponse{}
a3, _ := anypb.New(p2)
tests := []struct {
input *anypb.Any
}{
// case: untyped nil
{input: nil},
// case: input is a typed nil
{input: a1},
// case: input is typed with non-nil value
{input: a2},
// case: input is typed with non-nil value
{input: a3},
}
for _, test := range tests {
b, err := convertAPIBody(test.input)
assert.NotNil(t, b)
assert.NoError(t, err)
}
}
func TestAPIBodyProto(t *testing.T) {
var nilJSON json.RawMessage
tests := []struct {
input json.RawMessage
expectNil bool
}{
{input: nil, expectNil: true},
{input: nilJSON, expectNil: true},
{input: []byte(`{}`), expectNil: false},
{input: []byte(`{"@type":"type.googleapis.com/clutch.k8s.v1.Pod"}`), expectNil: false},
}
for _, test := range tests {
a, err := apiBodyProto(test.input)
if test.expectNil {
assert.Nil(t, a)
} else {
assert.NotNil(t, a)
}
assert.NoError(t, err)
}
}
......@@ -554,6 +554,102 @@ export namespace clutch {
public toJSON(): { [k: string]: any };
}
 
/** Properties of a RequestMetadata. */
interface IRequestMetadata {
/** RequestMetadata body */
body?: (google.protobuf.IAny|null);
}
/** Represents a RequestMetadata. */
class RequestMetadata implements IRequestMetadata {
/**
* Constructs a new RequestMetadata.
* @param [properties] Properties to set
*/
constructor(properties?: clutch.audit.v1.IRequestMetadata);
/** RequestMetadata body. */
public body?: (google.protobuf.IAny|null);
/**
* Verifies a RequestMetadata message.
* @param message Plain object to verify
* @returns `null` if valid, otherwise the reason why it is not
*/
public static verify(message: { [k: string]: any }): (string|null);
/**
* Creates a RequestMetadata message from a plain object. Also converts values to their respective internal types.
* @param object Plain object
* @returns RequestMetadata
*/
public static fromObject(object: { [k: string]: any }): clutch.audit.v1.RequestMetadata;
/**
* Creates a plain object from a RequestMetadata message. Also converts values to other types if specified.
* @param message RequestMetadata
* @param [options] Conversion options
* @returns Plain object
*/
public static toObject(message: clutch.audit.v1.RequestMetadata, options?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this RequestMetadata to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
}
/** Properties of a ResponseMetadata. */
interface IResponseMetadata {
/** ResponseMetadata body */
body?: (google.protobuf.IAny|null);
}
/** Represents a ResponseMetadata. */
class ResponseMetadata implements IResponseMetadata {
/**
* Constructs a new ResponseMetadata.
* @param [properties] Properties to set
*/
constructor(properties?: clutch.audit.v1.IResponseMetadata);
/** ResponseMetadata body. */
public body?: (google.protobuf.IAny|null);
/**
* Verifies a ResponseMetadata message.
* @param message Plain object to verify
* @returns `null` if valid, otherwise the reason why it is not
*/
public static verify(message: { [k: string]: any }): (string|null);
/**
* Creates a ResponseMetadata message from a plain object. Also converts values to their respective internal types.
* @param object Plain object
* @returns ResponseMetadata
*/
public static fromObject(object: { [k: string]: any }): clutch.audit.v1.ResponseMetadata;
/**
* Creates a plain object from a ResponseMetadata message. Also converts values to other types if specified.
* @param message ResponseMetadata
* @param [options] Conversion options
* @returns Plain object
*/
public static toObject(message: clutch.audit.v1.ResponseMetadata, options?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this ResponseMetadata to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
}
/** Properties of a RequestEvent. */
interface IRequestEvent {
 
......@@ -574,6 +670,12 @@ export namespace clutch {
 
/** RequestEvent resources */
resources?: (clutch.audit.v1.IResource[]|null);
/** RequestEvent requestMetadata */
requestMetadata?: (clutch.audit.v1.IRequestMetadata|null);
/** RequestEvent responseMetadata */
responseMetadata?: (clutch.audit.v1.IResponseMetadata|null);
}
 
/** Represents a RequestEvent. */
......@@ -603,6 +705,12 @@ export namespace clutch {
/** RequestEvent resources. */
public resources: clutch.audit.v1.IResource[];
 
/** RequestEvent requestMetadata. */
public requestMetadata?: (clutch.audit.v1.IRequestMetadata|null);
/** RequestEvent responseMetadata. */
public responseMetadata?: (clutch.audit.v1.IResponseMetadata|null);
/**
* Verifies a RequestEvent message.
* @param message Plain object to verify
......@@ -15710,6 +15818,60 @@ export namespace google {
}
}
 
/** Properties of an Any. */
interface IAny {
/** Any type_url */
type_url?: (string|null);
/** Any value */
value?: (Uint8Array|null);
}
/** Represents an Any. */
class Any implements IAny {
/**
* Constructs a new Any.
* @param [properties] Properties to set
*/
constructor(properties?: google.protobuf.IAny);
/** Any type_url. */
public type_url: string;
/** Any value. */
public value: Uint8Array;
/**
* Verifies an Any message.
* @param message Plain object to verify
* @returns `null` if valid, otherwise the reason why it is not
*/
public static verify(message: { [k: string]: any }): (string|null);
/**
* Creates an Any message from a plain object. Also converts values to their respective internal types.
* @param object Plain object
* @returns Any
*/
public static fromObject(object: { [k: string]: any }): google.protobuf.Any;
/**
* Creates a plain object from an Any message. Also converts values to other types if specified.
* @param message Any
* @param [options] Conversion options
* @returns Plain object
*/
public static toObject(message: google.protobuf.Any, options?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this Any to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
}
/** Properties of a Duration. */
interface IDuration {
 
......@@ -15818,60 +15980,6 @@ export namespace google {
public toJSON(): { [k: string]: any };
}
 
/** Properties of an Any. */
interface IAny {
/** Any type_url */
type_url?: (string|null);
/** Any value */
value?: (Uint8Array|null);
}
/** Represents an Any. */
class Any implements IAny {
/**
* Constructs a new Any.
* @param [properties] Properties to set
*/
constructor(properties?: google.protobuf.IAny);
/** Any type_url. */
public type_url: string;
/** Any value. */
public value: Uint8Array;
/**
* Verifies an Any message.
* @param message Plain object to verify
* @returns `null` if valid, otherwise the reason why it is not
*/
public static verify(message: { [k: string]: any }): (string|null);
/**
* Creates an Any message from a plain object. Also converts values to their respective internal types.
* @param object Plain object
* @returns Any
*/
public static fromObject(object: { [k: string]: any }): google.protobuf.Any;
/**
* Creates a plain object from an Any message. Also converts values to other types if specified.
* @param message Any
* @param [options] Conversion options
* @returns Plain object
*/
public static toObject(message: google.protobuf.Any, options?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this Any to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
}
/** Properties of a DoubleValue. */
interface IDoubleValue {
 
......
This diff is collapsed.
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment