From d6e0afe06c00d5425bbaaf391f28a09d5663bcb4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 20:20:22 +0000 Subject: [PATCH] Bump github.com/conductorone/baton-sdk from 0.4.8 to 0.4.13 Bumps [github.com/conductorone/baton-sdk](https://github.com/conductorone/baton-sdk) from 0.4.8 to 0.4.13. - [Release notes](https://github.com/conductorone/baton-sdk/releases) - [Commits](https://github.com/conductorone/baton-sdk/compare/v0.4.8...v0.4.13) --- updated-dependencies: - dependency-name: github.com/conductorone/baton-sdk dependency-version: 0.4.13 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 4 +- go.sum | 4 +- .../baton-sdk/pb/c1/config/v1/config.pb.go | 491 +++-- .../pb/c1/config/v1/config.pb.validate.go | 139 ++ .../baton-sdk/pkg/actions/actions.go | 2 +- .../conductorone/baton-sdk/pkg/cli/cli.go | 11 +- .../baton-sdk/pkg/cli/commands.go | 41 +- .../baton-sdk/pkg/cli/lambda_server__added.go | 41 +- .../baton-sdk/pkg/config/generate.go | 18 +- .../pkg/connectorbuilder/accounts.go | 127 ++ .../baton-sdk/pkg/connectorbuilder/actions.go | 171 ++ .../baton-sdk/pkg/connectorbuilder/assets.go | 11 + .../pkg/connectorbuilder/connectorbuilder.go | 1790 +++-------------- .../pkg/connectorbuilder/credentials.go | 105 + .../baton-sdk/pkg/connectorbuilder/events.go | 163 ++ .../pkg/connectorbuilder/resource_manager.go | 230 +++ .../connectorbuilder/resource_provisioner.go | 173 ++ .../pkg/connectorbuilder/resource_syncer.go | 294 +++ .../baton-sdk/pkg/connectorbuilder/tickets.go | 242 +++ .../baton-sdk/pkg/field/decode_hooks.go | 171 ++ .../baton-sdk/pkg/field/field_group.go | 16 + .../baton-sdk/pkg/field/fields.go | 24 + .../baton-sdk/pkg/field/marshal.go | 296 +-- .../baton-sdk/pkg/field/struct.go | 1 + .../conductorone/baton-sdk/pkg/sdk/version.go | 2 +- .../baton-sdk/pkg/session/grpc_session.go | 22 +- .../baton-sdk/pkg/session/json.go | 16 +- .../baton-sdk/pkg/session/memory.go | 22 +- .../baton-sdk/pkg/session/session.go | 20 +- .../conductorone/baton-sdk/pkg/sync/syncer.go | 10 +- .../baton-sdk/pkg/synccompactor/compactor.go | 1 + .../baton-sdk/pkg/types/resource/resource.go | 5 + .../baton-sdk/pkg/types/session_cache.go | 58 - .../baton-sdk/pkg/types/sessions/sessions.go | 52 + .../baton-sdk/pkg/ugrpc/interceptors.go | 12 +- .../baton-sdk/pkg/uotel/config.go | 2 +- vendor/modules.txt | 5 +- 37 files changed, 2830 insertions(+), 1962 deletions(-) create mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/accounts.go create mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/actions.go create mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/credentials.go create mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/events.go create mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_manager.go create mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_provisioner.go create mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_syncer.go create mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/tickets.go create mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/field/decode_hooks.go create mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/field/field_group.go delete mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/types/session_cache.go create mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/types/sessions/sessions.go diff --git a/go.mod b/go.mod index 1ad52336..a3beeb0f 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module github.com/conductorone/baton-sql -go 1.25 +go 1.25.2 require ( github.com/SAP/go-hdb v1.14.5 - github.com/conductorone/baton-sdk v0.4.8 + github.com/conductorone/baton-sdk v0.4.13 github.com/elliotchance/phpserialize v1.4.0 github.com/ennyjfrick/ruleguard-logfatal v0.0.2 github.com/go-sql-driver/mysql v1.9.2 diff --git a/go.sum b/go.sum index 1c9ab669..0c2435df 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/conductorone/baton-sdk v0.4.8 h1:bK32ANhtllTy0Obo7vugdMO7ebifzDe0JePgeiLzgwg= -github.com/conductorone/baton-sdk v0.4.8/go.mod h1:EYZ1Hq2NCSENzFLuTPdnarSXfb7ESjeXwDvDe0TPR3s= +github.com/conductorone/baton-sdk v0.4.13 h1:e2qXYPgZrevs9saI+bozfJ6+L2IEveYhK72k9cABUUs= +github.com/conductorone/baton-sdk v0.4.13/go.mod h1:UYJ9rgXowKzlHFSyDwh3NISWrDHZhf+yR4LNJLWMHUg= github.com/conductorone/dpop v0.2.4 h1:PaiDOX1gAIXtOJPxXf08GsGkpCuT/iECEjSJzLpi0zU= github.com/conductorone/dpop v0.2.4/go.mod h1:gyo8TtzB9SCFCsjsICH4IaLZ7y64CcrDXMOPBwfq/3s= github.com/conductorone/dpop/integrations/dpop_grpc v0.2.4 h1:lYxYi9/WTSL9sE96CO0QF2BY3kehs8dTTApI134TGCA= diff --git a/vendor/github.com/conductorone/baton-sdk/pb/c1/config/v1/config.pb.go b/vendor/github.com/conductorone/baton-sdk/pb/c1/config/v1/config.pb.go index ebbf484a..b480b6f9 100644 --- a/vendor/github.com/conductorone/baton-sdk/pb/c1/config/v1/config.pb.go +++ b/vendor/github.com/conductorone/baton-sdk/pb/c1/config/v1/config.pb.go @@ -143,6 +143,7 @@ type Configuration struct { CatalogId string `protobuf:"bytes,8,opt,name=catalog_id,json=catalogId,proto3" json:"catalog_id,omitempty"` SupportsExternalResources bool `protobuf:"varint,9,opt,name=supports_external_resources,json=supportsExternalResources,proto3" json:"supports_external_resources,omitempty"` RequiresExternalConnector bool `protobuf:"varint,10,opt,name=requires_external_connector,json=requiresExternalConnector,proto3" json:"requires_external_connector,omitempty"` + FieldGroups []*FieldGroup `protobuf:"bytes,11,rep,name=field_groups,json=fieldGroups,proto3" json:"field_groups,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -240,6 +241,13 @@ func (x *Configuration) GetRequiresExternalConnector() bool { return false } +func (x *Configuration) GetFieldGroups() []*FieldGroup { + if x != nil { + return x.FieldGroups + } + return nil +} + type Constraint struct { state protoimpl.MessageState `protogen:"open.v1"` Kind ConstraintKind `protobuf:"varint,1,opt,name=kind,proto3,enum=c1.config.v1.ConstraintKind" json:"kind,omitempty"` @@ -324,6 +332,74 @@ func (x *Constraint) GetIsFieldGroup() bool { return false } +type FieldGroup struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // Unique ID. + DisplayName string `protobuf:"bytes,2,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` + HelpText string `protobuf:"bytes,3,opt,name=help_text,json=helpText,proto3" json:"help_text,omitempty"` + Fields []string `protobuf:"bytes,4,rep,name=fields,proto3" json:"fields,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FieldGroup) Reset() { + *x = FieldGroup{} + mi := &file_c1_config_v1_config_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FieldGroup) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FieldGroup) ProtoMessage() {} + +func (x *FieldGroup) ProtoReflect() protoreflect.Message { + mi := &file_c1_config_v1_config_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FieldGroup.ProtoReflect.Descriptor instead. +func (*FieldGroup) Descriptor() ([]byte, []int) { + return file_c1_config_v1_config_proto_rawDescGZIP(), []int{2} +} + +func (x *FieldGroup) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *FieldGroup) GetDisplayName() string { + if x != nil { + return x.DisplayName + } + return "" +} + +func (x *FieldGroup) GetHelpText() string { + if x != nil { + return x.HelpText + } + return "" +} + +func (x *FieldGroup) GetFields() []string { + if x != nil { + return x.Fields + } + return nil +} + type Field struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // canonical name, typically in snake @@ -347,7 +423,7 @@ type Field struct { func (x *Field) Reset() { *x = Field{} - mi := &file_c1_config_v1_config_proto_msgTypes[2] + mi := &file_c1_config_v1_config_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -359,7 +435,7 @@ func (x *Field) String() string { func (*Field) ProtoMessage() {} func (x *Field) ProtoReflect() protoreflect.Message { - mi := &file_c1_config_v1_config_proto_msgTypes[2] + mi := &file_c1_config_v1_config_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -372,7 +448,7 @@ func (x *Field) ProtoReflect() protoreflect.Message { // Deprecated: Use Field.ProtoReflect.Descriptor instead. func (*Field) Descriptor() ([]byte, []int) { - return file_c1_config_v1_config_proto_rawDescGZIP(), []int{2} + return file_c1_config_v1_config_proto_rawDescGZIP(), []int{3} } func (x *Field) GetName() string { @@ -521,7 +597,7 @@ type IntField struct { func (x *IntField) Reset() { *x = IntField{} - mi := &file_c1_config_v1_config_proto_msgTypes[3] + mi := &file_c1_config_v1_config_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -533,7 +609,7 @@ func (x *IntField) String() string { func (*IntField) ProtoMessage() {} func (x *IntField) ProtoReflect() protoreflect.Message { - mi := &file_c1_config_v1_config_proto_msgTypes[3] + mi := &file_c1_config_v1_config_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -546,7 +622,7 @@ func (x *IntField) ProtoReflect() protoreflect.Message { // Deprecated: Use IntField.ProtoReflect.Descriptor instead. func (*IntField) Descriptor() ([]byte, []int) { - return file_c1_config_v1_config_proto_rawDescGZIP(), []int{3} + return file_c1_config_v1_config_proto_rawDescGZIP(), []int{4} } func (x *IntField) GetDefaultValue() int64 { @@ -573,7 +649,7 @@ type BoolField struct { func (x *BoolField) Reset() { *x = BoolField{} - mi := &file_c1_config_v1_config_proto_msgTypes[4] + mi := &file_c1_config_v1_config_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -585,7 +661,7 @@ func (x *BoolField) String() string { func (*BoolField) ProtoMessage() {} func (x *BoolField) ProtoReflect() protoreflect.Message { - mi := &file_c1_config_v1_config_proto_msgTypes[4] + mi := &file_c1_config_v1_config_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -598,7 +674,7 @@ func (x *BoolField) ProtoReflect() protoreflect.Message { // Deprecated: Use BoolField.ProtoReflect.Descriptor instead. func (*BoolField) Descriptor() ([]byte, []int) { - return file_c1_config_v1_config_proto_rawDescGZIP(), []int{4} + return file_c1_config_v1_config_proto_rawDescGZIP(), []int{5} } func (x *BoolField) GetDefaultValue() bool { @@ -625,7 +701,7 @@ type StringSliceField struct { func (x *StringSliceField) Reset() { *x = StringSliceField{} - mi := &file_c1_config_v1_config_proto_msgTypes[5] + mi := &file_c1_config_v1_config_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -637,7 +713,7 @@ func (x *StringSliceField) String() string { func (*StringSliceField) ProtoMessage() {} func (x *StringSliceField) ProtoReflect() protoreflect.Message { - mi := &file_c1_config_v1_config_proto_msgTypes[5] + mi := &file_c1_config_v1_config_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -650,7 +726,7 @@ func (x *StringSliceField) ProtoReflect() protoreflect.Message { // Deprecated: Use StringSliceField.ProtoReflect.Descriptor instead. func (*StringSliceField) Descriptor() ([]byte, []int) { - return file_c1_config_v1_config_proto_rawDescGZIP(), []int{5} + return file_c1_config_v1_config_proto_rawDescGZIP(), []int{6} } func (x *StringSliceField) GetDefaultValue() []string { @@ -677,7 +753,7 @@ type StringMapField struct { func (x *StringMapField) Reset() { *x = StringMapField{} - mi := &file_c1_config_v1_config_proto_msgTypes[6] + mi := &file_c1_config_v1_config_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -689,7 +765,7 @@ func (x *StringMapField) String() string { func (*StringMapField) ProtoMessage() {} func (x *StringMapField) ProtoReflect() protoreflect.Message { - mi := &file_c1_config_v1_config_proto_msgTypes[6] + mi := &file_c1_config_v1_config_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -702,7 +778,7 @@ func (x *StringMapField) ProtoReflect() protoreflect.Message { // Deprecated: Use StringMapField.ProtoReflect.Descriptor instead. func (*StringMapField) Descriptor() ([]byte, []int) { - return file_c1_config_v1_config_proto_rawDescGZIP(), []int{6} + return file_c1_config_v1_config_proto_rawDescGZIP(), []int{7} } func (x *StringMapField) GetDefaultValue() map[string]*anypb.Any { @@ -730,7 +806,7 @@ type StringFieldOption struct { func (x *StringFieldOption) Reset() { *x = StringFieldOption{} - mi := &file_c1_config_v1_config_proto_msgTypes[7] + mi := &file_c1_config_v1_config_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -742,7 +818,7 @@ func (x *StringFieldOption) String() string { func (*StringFieldOption) ProtoMessage() {} func (x *StringFieldOption) ProtoReflect() protoreflect.Message { - mi := &file_c1_config_v1_config_proto_msgTypes[7] + mi := &file_c1_config_v1_config_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -755,7 +831,7 @@ func (x *StringFieldOption) ProtoReflect() protoreflect.Message { // Deprecated: Use StringFieldOption.ProtoReflect.Descriptor instead. func (*StringFieldOption) Descriptor() ([]byte, []int) { - return file_c1_config_v1_config_proto_rawDescGZIP(), []int{7} + return file_c1_config_v1_config_proto_rawDescGZIP(), []int{8} } func (x *StringFieldOption) GetName() string { @@ -793,7 +869,7 @@ type StringField struct { func (x *StringField) Reset() { *x = StringField{} - mi := &file_c1_config_v1_config_proto_msgTypes[8] + mi := &file_c1_config_v1_config_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -805,7 +881,7 @@ func (x *StringField) String() string { func (*StringField) ProtoMessage() {} func (x *StringField) ProtoReflect() protoreflect.Message { - mi := &file_c1_config_v1_config_proto_msgTypes[8] + mi := &file_c1_config_v1_config_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -818,7 +894,7 @@ func (x *StringField) ProtoReflect() protoreflect.Message { // Deprecated: Use StringField.ProtoReflect.Descriptor instead. func (*StringField) Descriptor() ([]byte, []int) { - return file_c1_config_v1_config_proto_rawDescGZIP(), []int{8} + return file_c1_config_v1_config_proto_rawDescGZIP(), []int{9} } func (x *StringField) GetDefaultValue() string { @@ -864,7 +940,7 @@ var file_c1_config_v1_config_proto_rawDesc = string([]byte{ 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x1a, 0x18, 0x63, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x93, + 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd0, 0x03, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, @@ -890,147 +966,158 @@ var file_c1_config_v1_config_proto_rawDesc = string([]byte{ 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x19, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x6f, 0x72, 0x22, 0xea, 0x01, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, - 0x69, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x1c, 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x52, - 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x66, 0x69, 0x65, 0x6c, - 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, - 0x61, 0x72, 0x79, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, - 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, - 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1b, - 0x0a, 0x09, 0x68, 0x65, 0x6c, 0x70, 0x5f, 0x74, 0x65, 0x78, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x68, 0x65, 0x6c, 0x70, 0x54, 0x65, 0x78, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x69, - 0x73, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x73, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x47, 0x72, 0x6f, 0x75, - 0x70, 0x22, 0xab, 0x04, 0x0a, 0x05, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x68, 0x6f, 0x6c, - 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x6c, 0x61, 0x63, 0x65, - 0x68, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x71, - 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, 0x5f, 0x6f, 0x70, - 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x69, 0x73, 0x4f, 0x70, 0x73, 0x12, 0x1b, - 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x08, 0x69, 0x73, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x3e, 0x0a, 0x0c, 0x73, - 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x64, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x0b, - 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x35, 0x0a, 0x09, 0x69, - 0x6e, 0x74, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x65, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, - 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, - 0x74, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x46, 0x69, 0x65, - 0x6c, 0x64, 0x12, 0x38, 0x0a, 0x0a, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, - 0x18, 0x66, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x48, - 0x00, 0x52, 0x09, 0x62, 0x6f, 0x6f, 0x6c, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x4e, 0x0a, 0x12, - 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x65, - 0x6c, 0x64, 0x18, 0x67, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x6c, - 0x69, 0x63, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x10, 0x73, 0x74, 0x72, 0x69, - 0x6e, 0x67, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x48, 0x0a, 0x10, - 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6d, 0x61, 0x70, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, - 0x18, 0x68, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4d, 0x61, 0x70, 0x46, - 0x69, 0x65, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4d, 0x61, - 0x70, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x22, - 0x6e, 0x0a, 0x08, 0x49, 0x6e, 0x74, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x64, - 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x12, 0x33, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x18, 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x49, - 0x6e, 0x74, 0x36, 0x34, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x48, 0x00, 0x52, 0x05, 0x72, 0x75, 0x6c, - 0x65, 0x73, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x22, - 0x6e, 0x0a, 0x09, 0x42, 0x6f, 0x6f, 0x6c, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x23, 0x0a, 0x0d, - 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x12, 0x32, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x17, 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, - 0x42, 0x6f, 0x6f, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x48, 0x00, 0x52, 0x05, 0x72, 0x75, 0x6c, - 0x65, 0x73, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x22, - 0x7f, 0x0a, 0x10, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x46, 0x69, + 0x63, 0x74, 0x6f, 0x72, 0x12, 0x3b, 0x0a, 0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x67, 0x72, + 0x6f, 0x75, 0x70, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x31, 0x2e, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x47, + 0x72, 0x6f, 0x75, 0x70, 0x52, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, + 0x73, 0x22, 0xea, 0x01, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, + 0x12, 0x30, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, + 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, + 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, + 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61, + 0x6d, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, + 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x13, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x68, + 0x65, 0x6c, 0x70, 0x5f, 0x74, 0x65, 0x78, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x68, 0x65, 0x6c, 0x70, 0x54, 0x65, 0x78, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x69, 0x73, 0x5f, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0c, 0x69, 0x73, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x22, 0x78, + 0x0a, 0x0a, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x65, 0x6c, 0x70, 0x5f, 0x74, 0x65, 0x78, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x65, 0x6c, 0x70, 0x54, 0x65, 0x78, 0x74, + 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x22, 0xab, 0x04, 0x0a, 0x05, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, + 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x70, + 0x6c, 0x61, 0x63, 0x65, 0x68, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x68, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x1f, 0x0a, + 0x0b, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x15, + 0x0a, 0x06, 0x69, 0x73, 0x5f, 0x6f, 0x70, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, + 0x69, 0x73, 0x4f, 0x70, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x73, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x53, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x12, 0x3e, 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x18, 0x64, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x12, 0x35, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, + 0x65, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x48, 0x00, 0x52, + 0x08, 0x69, 0x6e, 0x74, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x38, 0x0a, 0x0a, 0x62, 0x6f, 0x6f, + 0x6c, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x66, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, + 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6f, 0x6f, + 0x6c, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x09, 0x62, 0x6f, 0x6f, 0x6c, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x12, 0x4e, 0x0a, 0x12, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x6c, + 0x69, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x67, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1e, 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x48, + 0x00, 0x52, 0x10, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x12, 0x48, 0x0a, 0x10, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6d, 0x61, + 0x70, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x68, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, + 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x4d, 0x61, 0x70, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x73, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4d, 0x61, 0x70, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x07, 0x0a, + 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x22, 0x6e, 0x0a, 0x08, 0x49, 0x6e, 0x74, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x33, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x52, 0x75, 0x6c, 0x65, 0x73, + 0x48, 0x00, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, + 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x22, 0x6e, 0x0a, 0x09, 0x42, 0x6f, 0x6f, 0x6c, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, - 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3c, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, - 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x53, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, + 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x32, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, + 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, + 0x48, 0x00, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, + 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x22, 0x7f, 0x0a, 0x10, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x53, 0x6c, 0x69, 0x63, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, + 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, + 0x3c, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, + 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, + 0x73, 0x48, 0x00, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, + 0x06, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x22, 0xff, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x4d, 0x61, 0x70, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x53, 0x0a, 0x0d, 0x64, 0x65, + 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x2e, 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4d, 0x61, 0x70, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x2e, + 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, + 0x37, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, + 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x4d, 0x61, 0x70, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x48, 0x00, 0x52, 0x05, + 0x72, 0x75, 0x6c, 0x65, 0x73, 0x88, 0x01, 0x01, 0x1a, 0x55, 0x0a, 0x11, 0x44, 0x65, 0x66, 0x61, + 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, + 0x08, 0x0a, 0x06, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x22, 0x60, 0x0a, 0x11, 0x53, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, + 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x8f, 0x02, 0x0a, 0x0b, + 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x64, + 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x12, 0x34, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x48, 0x00, 0x52, 0x05, 0x72, 0x75, - 0x6c, 0x65, 0x73, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, - 0x22, 0xff, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4d, 0x61, 0x70, 0x46, 0x69, - 0x65, 0x6c, 0x64, 0x12, 0x53, 0x0a, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x63, 0x31, 0x2e, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, - 0x4d, 0x61, 0x70, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, - 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, - 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4d, 0x61, 0x70, - 0x52, 0x75, 0x6c, 0x65, 0x73, 0x48, 0x00, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x88, 0x01, - 0x01, 0x1a, 0x55, 0x0a, 0x11, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x72, 0x75, 0x6c, - 0x65, 0x73, 0x22, 0x60, 0x0a, 0x11, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x46, 0x69, 0x65, 0x6c, - 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x8f, 0x02, 0x0a, 0x0b, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x46, - 0x69, 0x65, 0x6c, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x64, 0x65, 0x66, - 0x61, 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x34, 0x0a, 0x05, 0x72, 0x75, 0x6c, - 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x75, - 0x6c, 0x65, 0x73, 0x48, 0x00, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x88, 0x01, 0x01, 0x12, - 0x31, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, - 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, - 0x69, 0x6e, 0x67, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x65, 0x78, - 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, - 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, - 0x73, 0x12, 0x39, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x08, 0x0a, 0x06, - 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x2a, 0xc4, 0x01, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x73, 0x74, - 0x72, 0x61, 0x69, 0x6e, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x1b, 0x43, 0x4f, 0x4e, - 0x53, 0x54, 0x52, 0x41, 0x49, 0x4e, 0x54, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, - 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x25, 0x0a, 0x21, 0x43, 0x4f, - 0x4e, 0x53, 0x54, 0x52, 0x41, 0x49, 0x4e, 0x54, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x52, 0x45, - 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x5f, 0x54, 0x4f, 0x47, 0x45, 0x54, 0x48, 0x45, 0x52, 0x10, - 0x01, 0x12, 0x20, 0x0a, 0x1c, 0x43, 0x4f, 0x4e, 0x53, 0x54, 0x52, 0x41, 0x49, 0x4e, 0x54, 0x5f, - 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x41, 0x54, 0x5f, 0x4c, 0x45, 0x41, 0x53, 0x54, 0x5f, 0x4f, 0x4e, - 0x45, 0x10, 0x02, 0x12, 0x26, 0x0a, 0x22, 0x43, 0x4f, 0x4e, 0x53, 0x54, 0x52, 0x41, 0x49, 0x4e, - 0x54, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x4d, 0x55, 0x54, 0x55, 0x41, 0x4c, 0x4c, 0x59, 0x5f, - 0x45, 0x58, 0x43, 0x4c, 0x55, 0x53, 0x49, 0x56, 0x45, 0x10, 0x03, 0x12, 0x20, 0x0a, 0x1c, 0x43, - 0x4f, 0x4e, 0x53, 0x54, 0x52, 0x41, 0x49, 0x4e, 0x54, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x44, - 0x45, 0x50, 0x45, 0x4e, 0x44, 0x45, 0x4e, 0x54, 0x5f, 0x4f, 0x4e, 0x10, 0x04, 0x2a, 0xc9, 0x01, - 0x0a, 0x0f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x26, 0x0a, 0x22, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x49, 0x45, 0x4c, - 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x54, 0x45, 0x58, 0x54, 0x5f, 0x55, 0x4e, 0x53, 0x50, - 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x54, 0x52, - 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, - 0x41, 0x4e, 0x44, 0x4f, 0x4d, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x54, 0x52, 0x49, 0x4e, - 0x47, 0x5f, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4f, 0x41, 0x55, - 0x54, 0x48, 0x32, 0x10, 0x02, 0x12, 0x2f, 0x0a, 0x2b, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x5f, - 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, - 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x44, 0x45, 0x52, 0x49, 0x56, 0x45, 0x44, 0x5f, 0x4f, 0x50, 0x54, - 0x49, 0x4f, 0x4e, 0x53, 0x10, 0x03, 0x12, 0x21, 0x0a, 0x1d, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, - 0x5f, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, - 0x5f, 0x55, 0x50, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x04, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x64, 0x75, 0x63, 0x74, 0x6f, - 0x72, 0x6f, 0x6e, 0x65, 0x2f, 0x62, 0x61, 0x74, 0x6f, 0x6e, 0x2d, 0x73, 0x64, 0x6b, 0x2f, 0x70, - 0x62, 0x2f, 0x63, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x76, 0x31, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6c, 0x65, 0x73, 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x63, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x54, + 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x61, 0x6c, 0x6c, + 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x45, 0x78, + 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x39, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x63, 0x31, 0x2e, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x2a, 0xc4, 0x01, + 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x4b, 0x69, 0x6e, 0x64, + 0x12, 0x1f, 0x0a, 0x1b, 0x43, 0x4f, 0x4e, 0x53, 0x54, 0x52, 0x41, 0x49, 0x4e, 0x54, 0x5f, 0x4b, + 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, + 0x00, 0x12, 0x25, 0x0a, 0x21, 0x43, 0x4f, 0x4e, 0x53, 0x54, 0x52, 0x41, 0x49, 0x4e, 0x54, 0x5f, + 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x5f, 0x54, 0x4f, + 0x47, 0x45, 0x54, 0x48, 0x45, 0x52, 0x10, 0x01, 0x12, 0x20, 0x0a, 0x1c, 0x43, 0x4f, 0x4e, 0x53, + 0x54, 0x52, 0x41, 0x49, 0x4e, 0x54, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x41, 0x54, 0x5f, 0x4c, + 0x45, 0x41, 0x53, 0x54, 0x5f, 0x4f, 0x4e, 0x45, 0x10, 0x02, 0x12, 0x26, 0x0a, 0x22, 0x43, 0x4f, + 0x4e, 0x53, 0x54, 0x52, 0x41, 0x49, 0x4e, 0x54, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x4d, 0x55, + 0x54, 0x55, 0x41, 0x4c, 0x4c, 0x59, 0x5f, 0x45, 0x58, 0x43, 0x4c, 0x55, 0x53, 0x49, 0x56, 0x45, + 0x10, 0x03, 0x12, 0x20, 0x0a, 0x1c, 0x43, 0x4f, 0x4e, 0x53, 0x54, 0x52, 0x41, 0x49, 0x4e, 0x54, + 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x44, 0x45, 0x50, 0x45, 0x4e, 0x44, 0x45, 0x4e, 0x54, 0x5f, + 0x4f, 0x4e, 0x10, 0x04, 0x2a, 0xc9, 0x01, 0x0a, 0x0f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x26, 0x0a, 0x22, 0x53, 0x54, 0x52, 0x49, + 0x4e, 0x47, 0x5f, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x54, 0x45, + 0x58, 0x54, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x49, 0x45, 0x4c, 0x44, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x41, 0x4e, 0x44, 0x4f, 0x4d, 0x10, 0x01, 0x12, 0x1c, + 0x0a, 0x18, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x4f, 0x41, 0x55, 0x54, 0x48, 0x32, 0x10, 0x02, 0x12, 0x2f, 0x0a, 0x2b, + 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x44, 0x45, 0x52, 0x49, + 0x56, 0x45, 0x44, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x53, 0x10, 0x03, 0x12, 0x21, 0x0a, + 0x1d, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x55, 0x50, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x04, + 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, + 0x6f, 0x6e, 0x64, 0x75, 0x63, 0x74, 0x6f, 0x72, 0x6f, 0x6e, 0x65, 0x2f, 0x62, 0x61, 0x74, 0x6f, + 0x6e, 0x2d, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -1046,50 +1133,52 @@ func file_c1_config_v1_config_proto_rawDescGZIP() []byte { } var file_c1_config_v1_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_c1_config_v1_config_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_c1_config_v1_config_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_c1_config_v1_config_proto_goTypes = []any{ (ConstraintKind)(0), // 0: c1.config.v1.ConstraintKind (StringFieldType)(0), // 1: c1.config.v1.StringFieldType (*Configuration)(nil), // 2: c1.config.v1.Configuration (*Constraint)(nil), // 3: c1.config.v1.Constraint - (*Field)(nil), // 4: c1.config.v1.Field - (*IntField)(nil), // 5: c1.config.v1.IntField - (*BoolField)(nil), // 6: c1.config.v1.BoolField - (*StringSliceField)(nil), // 7: c1.config.v1.StringSliceField - (*StringMapField)(nil), // 8: c1.config.v1.StringMapField - (*StringFieldOption)(nil), // 9: c1.config.v1.StringFieldOption - (*StringField)(nil), // 10: c1.config.v1.StringField - nil, // 11: c1.config.v1.StringMapField.DefaultValueEntry - (*Int64Rules)(nil), // 12: c1.config.v1.Int64Rules - (*BoolRules)(nil), // 13: c1.config.v1.BoolRules - (*RepeatedStringRules)(nil), // 14: c1.config.v1.RepeatedStringRules - (*StringMapRules)(nil), // 15: c1.config.v1.StringMapRules - (*StringRules)(nil), // 16: c1.config.v1.StringRules - (*anypb.Any)(nil), // 17: google.protobuf.Any + (*FieldGroup)(nil), // 4: c1.config.v1.FieldGroup + (*Field)(nil), // 5: c1.config.v1.Field + (*IntField)(nil), // 6: c1.config.v1.IntField + (*BoolField)(nil), // 7: c1.config.v1.BoolField + (*StringSliceField)(nil), // 8: c1.config.v1.StringSliceField + (*StringMapField)(nil), // 9: c1.config.v1.StringMapField + (*StringFieldOption)(nil), // 10: c1.config.v1.StringFieldOption + (*StringField)(nil), // 11: c1.config.v1.StringField + nil, // 12: c1.config.v1.StringMapField.DefaultValueEntry + (*Int64Rules)(nil), // 13: c1.config.v1.Int64Rules + (*BoolRules)(nil), // 14: c1.config.v1.BoolRules + (*RepeatedStringRules)(nil), // 15: c1.config.v1.RepeatedStringRules + (*StringMapRules)(nil), // 16: c1.config.v1.StringMapRules + (*StringRules)(nil), // 17: c1.config.v1.StringRules + (*anypb.Any)(nil), // 18: google.protobuf.Any } var file_c1_config_v1_config_proto_depIdxs = []int32{ - 4, // 0: c1.config.v1.Configuration.fields:type_name -> c1.config.v1.Field + 5, // 0: c1.config.v1.Configuration.fields:type_name -> c1.config.v1.Field 3, // 1: c1.config.v1.Configuration.constraints:type_name -> c1.config.v1.Constraint - 0, // 2: c1.config.v1.Constraint.kind:type_name -> c1.config.v1.ConstraintKind - 10, // 3: c1.config.v1.Field.string_field:type_name -> c1.config.v1.StringField - 5, // 4: c1.config.v1.Field.int_field:type_name -> c1.config.v1.IntField - 6, // 5: c1.config.v1.Field.bool_field:type_name -> c1.config.v1.BoolField - 7, // 6: c1.config.v1.Field.string_slice_field:type_name -> c1.config.v1.StringSliceField - 8, // 7: c1.config.v1.Field.string_map_field:type_name -> c1.config.v1.StringMapField - 12, // 8: c1.config.v1.IntField.rules:type_name -> c1.config.v1.Int64Rules - 13, // 9: c1.config.v1.BoolField.rules:type_name -> c1.config.v1.BoolRules - 14, // 10: c1.config.v1.StringSliceField.rules:type_name -> c1.config.v1.RepeatedStringRules - 11, // 11: c1.config.v1.StringMapField.default_value:type_name -> c1.config.v1.StringMapField.DefaultValueEntry - 15, // 12: c1.config.v1.StringMapField.rules:type_name -> c1.config.v1.StringMapRules - 16, // 13: c1.config.v1.StringField.rules:type_name -> c1.config.v1.StringRules - 1, // 14: c1.config.v1.StringField.type:type_name -> c1.config.v1.StringFieldType - 9, // 15: c1.config.v1.StringField.options:type_name -> c1.config.v1.StringFieldOption - 17, // 16: c1.config.v1.StringMapField.DefaultValueEntry.value:type_name -> google.protobuf.Any - 17, // [17:17] is the sub-list for method output_type - 17, // [17:17] is the sub-list for method input_type - 17, // [17:17] is the sub-list for extension type_name - 17, // [17:17] is the sub-list for extension extendee - 0, // [0:17] is the sub-list for field type_name + 4, // 2: c1.config.v1.Configuration.field_groups:type_name -> c1.config.v1.FieldGroup + 0, // 3: c1.config.v1.Constraint.kind:type_name -> c1.config.v1.ConstraintKind + 11, // 4: c1.config.v1.Field.string_field:type_name -> c1.config.v1.StringField + 6, // 5: c1.config.v1.Field.int_field:type_name -> c1.config.v1.IntField + 7, // 6: c1.config.v1.Field.bool_field:type_name -> c1.config.v1.BoolField + 8, // 7: c1.config.v1.Field.string_slice_field:type_name -> c1.config.v1.StringSliceField + 9, // 8: c1.config.v1.Field.string_map_field:type_name -> c1.config.v1.StringMapField + 13, // 9: c1.config.v1.IntField.rules:type_name -> c1.config.v1.Int64Rules + 14, // 10: c1.config.v1.BoolField.rules:type_name -> c1.config.v1.BoolRules + 15, // 11: c1.config.v1.StringSliceField.rules:type_name -> c1.config.v1.RepeatedStringRules + 12, // 12: c1.config.v1.StringMapField.default_value:type_name -> c1.config.v1.StringMapField.DefaultValueEntry + 16, // 13: c1.config.v1.StringMapField.rules:type_name -> c1.config.v1.StringMapRules + 17, // 14: c1.config.v1.StringField.rules:type_name -> c1.config.v1.StringRules + 1, // 15: c1.config.v1.StringField.type:type_name -> c1.config.v1.StringFieldType + 10, // 16: c1.config.v1.StringField.options:type_name -> c1.config.v1.StringFieldOption + 18, // 17: c1.config.v1.StringMapField.DefaultValueEntry.value:type_name -> google.protobuf.Any + 18, // [18:18] is the sub-list for method output_type + 18, // [18:18] is the sub-list for method input_type + 18, // [18:18] is the sub-list for extension type_name + 18, // [18:18] is the sub-list for extension extendee + 0, // [0:18] is the sub-list for field type_name } func init() { file_c1_config_v1_config_proto_init() } @@ -1098,25 +1187,25 @@ func file_c1_config_v1_config_proto_init() { return } file_c1_config_v1_rules_proto_init() - file_c1_config_v1_config_proto_msgTypes[2].OneofWrappers = []any{ + file_c1_config_v1_config_proto_msgTypes[3].OneofWrappers = []any{ (*Field_StringField)(nil), (*Field_IntField)(nil), (*Field_BoolField)(nil), (*Field_StringSliceField)(nil), (*Field_StringMapField)(nil), } - file_c1_config_v1_config_proto_msgTypes[3].OneofWrappers = []any{} file_c1_config_v1_config_proto_msgTypes[4].OneofWrappers = []any{} file_c1_config_v1_config_proto_msgTypes[5].OneofWrappers = []any{} file_c1_config_v1_config_proto_msgTypes[6].OneofWrappers = []any{} - file_c1_config_v1_config_proto_msgTypes[8].OneofWrappers = []any{} + file_c1_config_v1_config_proto_msgTypes[7].OneofWrappers = []any{} + file_c1_config_v1_config_proto_msgTypes[9].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_c1_config_v1_config_proto_rawDesc), len(file_c1_config_v1_config_proto_rawDesc)), NumEnums: 2, - NumMessages: 10, + NumMessages: 11, NumExtensions: 0, NumServices: 0, }, diff --git a/vendor/github.com/conductorone/baton-sdk/pb/c1/config/v1/config.pb.validate.go b/vendor/github.com/conductorone/baton-sdk/pb/c1/config/v1/config.pb.validate.go index e2114f8d..523c7c7b 100644 --- a/vendor/github.com/conductorone/baton-sdk/pb/c1/config/v1/config.pb.validate.go +++ b/vendor/github.com/conductorone/baton-sdk/pb/c1/config/v1/config.pb.validate.go @@ -139,6 +139,40 @@ func (m *Configuration) validate(all bool) error { // no validation rules for RequiresExternalConnector + for idx, item := range m.GetFieldGroups() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ConfigurationValidationError{ + field: fmt.Sprintf("FieldGroups[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ConfigurationValidationError{ + field: fmt.Sprintf("FieldGroups[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ConfigurationValidationError{ + field: fmt.Sprintf("FieldGroups[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + if len(errors) > 0 { return ConfigurationMultiError(errors) } @@ -324,6 +358,111 @@ var _ interface { ErrorName() string } = ConstraintValidationError{} +// Validate checks the field values on FieldGroup with the rules defined in the +// proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *FieldGroup) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on FieldGroup with the rules defined in +// the proto definition for this message. If any rules are violated, the +// result is a list of violation errors wrapped in FieldGroupMultiError, or +// nil if none found. +func (m *FieldGroup) ValidateAll() error { + return m.validate(true) +} + +func (m *FieldGroup) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Name + + // no validation rules for DisplayName + + // no validation rules for HelpText + + if len(errors) > 0 { + return FieldGroupMultiError(errors) + } + + return nil +} + +// FieldGroupMultiError is an error wrapping multiple validation errors +// returned by FieldGroup.ValidateAll() if the designated constraints aren't met. +type FieldGroupMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m FieldGroupMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m FieldGroupMultiError) AllErrors() []error { return m } + +// FieldGroupValidationError is the validation error returned by +// FieldGroup.Validate if the designated constraints aren't met. +type FieldGroupValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e FieldGroupValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e FieldGroupValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e FieldGroupValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e FieldGroupValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e FieldGroupValidationError) ErrorName() string { return "FieldGroupValidationError" } + +// Error satisfies the builtin error interface +func (e FieldGroupValidationError) 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 %sFieldGroup.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = FieldGroupValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = FieldGroupValidationError{} + // Validate checks the field values on Field with the rules defined in the // proto definition for this message. If any rules are violated, the first // error encountered is returned, or nil if there are no violations. diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/actions/actions.go b/vendor/github.com/conductorone/baton-sdk/pkg/actions/actions.go index a78c349b..58561873 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/actions/actions.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/actions/actions.go @@ -233,7 +233,7 @@ func (a *ActionManager) InvokeAction(ctx context.Context, name string, args *str // If handler takes longer than an hour, return status failed. go func() { oa.SetStatus(ctx, v2.BatonActionStatus_BATON_ACTION_STATUS_RUNNING) - handlerCtx, cancel := context.WithTimeoutCause(ctx, 1*time.Hour, errors.New("action handler timed out")) + handlerCtx, cancel := context.WithTimeoutCause(context.Background(), 1*time.Hour, errors.New("action handler timed out")) defer cancel() var oaErr error oa.Rv, oa.Annos, oaErr = handler(handlerCtx, args) diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/cli/cli.go b/vendor/github.com/conductorone/baton-sdk/pkg/cli/cli.go index 32b0541f..e9d54fbd 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/cli/cli.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/cli/cli.go @@ -7,6 +7,7 @@ import ( "github.com/conductorone/baton-sdk/pkg/field" "github.com/conductorone/baton-sdk/pkg/types" + "github.com/conductorone/baton-sdk/pkg/types/sessions" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" @@ -17,15 +18,15 @@ import ( type GetConnectorFunc[T field.Configurable] func(ctx context.Context, cfg T) (types.ConnectorServer, error) // WithSessionCache creates a session cache using the provided constructor and adds it to the context. -func WithSessionCache(ctx context.Context, constructor types.SessionCacheConstructor) (context.Context, error) { +func WithSessionCache(ctx context.Context, constructor sessions.SessionStoreConstructor) (context.Context, error) { sessionCache, err := constructor(ctx) if err != nil { return ctx, fmt.Errorf("failed to create session cache: %w", err) } - return context.WithValue(ctx, types.SessionCacheKey{}, sessionCache), nil + return context.WithValue(ctx, sessions.SessionStoreKey{}, sessionCache), nil } -func MakeGenericConfiguration[T field.Configurable](v *viper.Viper) (T, error) { +func MakeGenericConfiguration[T field.Configurable](v *viper.Viper, opts ...field.DecodeHookOption) (T, error) { // Create an instance of the struct type T using reflection var config T // Create a zero-value instance of T @@ -37,8 +38,8 @@ func MakeGenericConfiguration[T field.Configurable](v *viper.Viper) (T, error) { return config, fmt.Errorf("cannot convert *viper.Viper to %T", config) } - // Unmarshal into the config struct - err := v.Unmarshal(&config) + // Unmarshal into the config struct with any decode hook options provided + err := v.Unmarshal(&config, viper.DecodeHook(field.ComposeDecodeHookFunc(opts...))) if err != nil { return config, fmt.Errorf("failed to unmarshal config: %w", err) } diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/cli/commands.go b/vendor/github.com/conductorone/baton-sdk/pkg/cli/commands.go index 3f728655..ffdd6190 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/cli/commands.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/cli/commands.go @@ -27,7 +27,7 @@ import ( "github.com/conductorone/baton-sdk/pkg/field" "github.com/conductorone/baton-sdk/pkg/logging" "github.com/conductorone/baton-sdk/pkg/session" - "github.com/conductorone/baton-sdk/pkg/types" + "github.com/conductorone/baton-sdk/pkg/types/sessions" "github.com/conductorone/baton-sdk/pkg/uotel" ) @@ -38,7 +38,7 @@ const ( type ContrainstSetter func(*cobra.Command, field.Configuration) error // defaultSessionCacheConstructor creates a default in-memory session cache. -func defaultSessionCacheConstructor(ctx context.Context, opt ...types.SessionCacheConstructorOption) (types.SessionCache, error) { +func defaultSessionCacheConstructor(ctx context.Context, opt ...sessions.SessionStoreConstructorOption) (sessions.SessionStore, error) { return session.NewMemorySessionCache(ctx, opt...) } @@ -98,8 +98,14 @@ func MakeMainCommand[T field.Configurable]( } } + readFromPath := true + decodeOpts := field.WithAdditionalDecodeHooks(field.FileUploadDecodeHook(readFromPath)) + t, err := MakeGenericConfiguration[T](v, decodeOpts) + if err != nil { + return fmt.Errorf("failed to make configuration: %w", err) + } // validate required fields and relationship constraints - if err := field.Validate(confschema, v); err != nil { + if err := field.Validate(confschema, t); err != nil { return err } @@ -299,11 +305,6 @@ func MakeMainCommand[T field.Configurable]( opts = append(opts, connectorrunner.WithSkipEntitlementsAndGrants(v.GetBool("skip-entitlements-and-grants"))) - t, err := MakeGenericConfiguration[T](v) - if err != nil { - return fmt.Errorf("failed to make configuration: %w", err) - } - // Create session cache and add to context runCtx, err = WithSessionCache(runCtx, defaultSessionCacheConstructor) if err != nil { @@ -412,14 +413,16 @@ func MakeGRPCServerCommand[T field.Configurable]( l := ctxzap.Extract(runCtx) l.Debug("starting grpc server") - // validate required fields and relationship constraints - if err := field.Validate(confschema, v); err != nil { - return err - } - t, err := MakeGenericConfiguration[T](v) + readFromPath := true + decodeOpts := field.WithAdditionalDecodeHooks(field.FileUploadDecodeHook(readFromPath)) + t, err := MakeGenericConfiguration[T](v, decodeOpts) if err != nil { return fmt.Errorf("failed to make configuration: %w", err) } + // validate required fields and relationship constraints + if err := field.Validate(confschema, t); err != nil { + return err + } // Create session cache and add to context runCtx, err = WithSessionCache(runCtx, defaultSessionCacheConstructor) @@ -553,14 +556,16 @@ func MakeCapabilitiesCommand[T field.Configurable]( return err } - // validate required fields and relationship constraints - if err := field.Validate(confschema, v); err != nil { - return err - } - t, err := MakeGenericConfiguration[T](v) + readFromPath := true + decodeOpts := field.WithAdditionalDecodeHooks(field.FileUploadDecodeHook(readFromPath)) + t, err := MakeGenericConfiguration[T](v, decodeOpts) if err != nil { return fmt.Errorf("failed to make configuration: %w", err) } + // validate required fields and relationship constraints + if err := field.Validate(confschema, t); err != nil { + return err + } // Create session cache and add to context runCtx, err = WithSessionCache(runCtx, defaultSessionCacheConstructor) diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/cli/lambda_server__added.go b/vendor/github.com/conductorone/baton-sdk/pkg/cli/lambda_server__added.go index 28ca3713..21ecbb20 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/cli/lambda_server__added.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/cli/lambda_server__added.go @@ -29,7 +29,7 @@ import ( c1_lambda_config "github.com/conductorone/baton-sdk/pkg/lambda/grpc/config" "github.com/conductorone/baton-sdk/pkg/lambda/grpc/middleware" "github.com/conductorone/baton-sdk/pkg/session" - "github.com/conductorone/baton-sdk/pkg/types" + "github.com/conductorone/baton-sdk/pkg/types/sessions" "google.golang.org/grpc" ) @@ -67,12 +67,16 @@ func OptionallyAddLambdaCommand[T field.Configurable]( logLevel = "info" } - initalLogFields := map[string]interface{}{ - "tenant": os.Getenv("tenant"), - "connector": os.Getenv("connector"), - "installation": os.Getenv("installation"), - "app": os.Getenv("app"), - "version": os.Getenv("version"), + initialLogFields := map[string]interface{}{ + "tenant_id": os.Getenv("tenant"), + "connector_id": os.Getenv("connector"), + "app_id": os.Getenv("app"), + "release_version": os.Getenv("version"), + "installation": os.Getenv("installation"), + "catalog_id": os.Getenv("catalog_id"), + "catalog_name": os.Getenv("catalog_name"), + "tenant_name": os.Getenv("tenant_name"), + "tenant_is_internal": os.Getenv("tenant_is_internal"), } runCtx, err := initLogger( @@ -80,13 +84,13 @@ func OptionallyAddLambdaCommand[T field.Configurable]( name, logging.WithLogFormat(v.GetString("log-format")), logging.WithLogLevel(logLevel), - logging.WithInitialFields(initalLogFields), + logging.WithInitialFields(initialLogFields), ) if err != nil { return err } - runCtx, otelShutdown, err := initOtel(runCtx, name, v, initalLogFields) + runCtx, otelShutdown, err := initOtel(runCtx, name, v, initialLogFields) if err != nil { return err } @@ -141,7 +145,10 @@ func OptionallyAddLambdaCommand[T field.Configurable]( return fmt.Errorf("lambda-run: failed to unmarshal decrypted config: %w", err) } - t, err := MakeGenericConfiguration[T](v) + // parse content directly for lambdas, don't read from file + readFromPath := false + decodeOpts := field.WithAdditionalDecodeHooks(field.FileUploadDecodeHook(readFromPath)) + t, err := MakeGenericConfiguration[T](v, decodeOpts) if err != nil { return fmt.Errorf("lambda-run: failed to make generic configuration: %w", err) } @@ -151,7 +158,15 @@ func OptionallyAddLambdaCommand[T field.Configurable]( cfg.Set(k, v) } default: - err = mapstructure.Decode(configStruct.AsMap(), cfg) + // Use mapstructure with decode hook for file upload fields + decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + DecodeHook: field.ComposeDecodeHookFunc(decodeOpts), + Result: cfg, + }) + if err != nil { + return fmt.Errorf("lambda-run: failed to create decoder: %w", err) + } + err = decoder.Decode(configStruct.AsMap()) if err != nil { return fmt.Errorf("lambda-run: failed to decode config: %w", err) } @@ -223,8 +238,8 @@ func OptionallyAddLambdaCommand[T field.Configurable]( } // createSessionCacheConstructor creates a session cache constructor function that uses the provided gRPC client -func createSessionCacheConstructor(grpcClient grpc.ClientConnInterface) types.SessionCacheConstructor { - return func(ctx context.Context, opt ...types.SessionCacheConstructorOption) (types.SessionCache, error) { +func createSessionCacheConstructor(grpcClient grpc.ClientConnInterface) sessions.SessionStoreConstructor { + return func(ctx context.Context, opt ...sessions.SessionStoreConstructorOption) (sessions.SessionStore, error) { // Create the gRPC session client using the same gRPC connection client := pb_connector_api.NewBatonSessionServiceClient(grpcClient) // Create and return the session cache diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/config/generate.go b/vendor/github.com/conductorone/baton-sdk/pkg/config/generate.go index 25498beb..6ce7100d 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/config/generate.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/config/generate.go @@ -68,7 +68,11 @@ func Generate(name string, schema field.Configuration) { } switch f.Variant { case field.StringVariant: - nf.FieldType = "string" + if f.ConnectorConfig.FieldType == field.FileUpload { + nf.FieldType = "[]byte" + } else { + nf.FieldType = "string" + } case field.BoolVariant: nf.FieldType = "bool" case field.IntVariant: @@ -108,7 +112,7 @@ type {{ .StructName }} struct { {{- end }} } -func (c* {{ .StructName }}) findFieldByTag(tagValue string) (any, bool) { +func (c *{{ .StructName }}) findFieldByTag(tagValue string) (any, bool) { v := reflect.ValueOf(c).Elem() // Dereference pointer to struct t := v.Type() @@ -140,11 +144,13 @@ func (c *{{ .StructName }}) GetString(fieldName string) string { if !ok { return "" } - t, ok := v.(string) - if !ok { - panic("wrong type") + if t, ok := v.(string); ok { + return t } - return t + if t, ok := v.([]byte); ok { + return string(t) + } + panic("wrong type") } func (c *{{ .StructName }}) GetInt(fieldName string) int { diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/accounts.go b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/accounts.go new file mode 100644 index 00000000..c7f118b3 --- /dev/null +++ b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/accounts.go @@ -0,0 +1,127 @@ +package connectorbuilder + +import ( + "context" + "fmt" + + v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + "github.com/conductorone/baton-sdk/pkg/annotations" + "github.com/conductorone/baton-sdk/pkg/crypto" + "github.com/conductorone/baton-sdk/pkg/types/tasks" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// CreateAccountResponse is a semi-opaque type returned from CreateAccount operations. +// +// This is used to communicate the result of account creation back to Baton. +type CreateAccountResponse interface { + proto.Message + GetIsCreateAccountResult() bool +} + +// AccountManager extends ResourceSyncer to add capabilities for managing user accounts. +// +// Implementing this interface indicates the connector supports creating accounts +// in the external system. A resource type should implement this interface if it +// represents users or accounts that can be provisioned. +type AccountManager interface { + ResourceSyncer + AccountManagerLimited +} + +type AccountManagerLimited interface { + CreateAccount(ctx context.Context, + accountInfo *v2.AccountInfo, + credentialOptions *v2.LocalCredentialOptions) (CreateAccountResponse, []*v2.PlaintextData, annotations.Annotations, error) + CreateAccountCapabilityDetails(ctx context.Context) (*v2.CredentialDetailsAccountProvisioning, annotations.Annotations, error) +} + +type OldAccountManager interface { + ResourceSyncer + CreateAccount(ctx context.Context, + accountInfo *v2.AccountInfo, + credentialOptions *v2.CredentialOptions) (CreateAccountResponse, []*v2.PlaintextData, annotations.Annotations, error) +} + +func (b *builder) CreateAccount(ctx context.Context, request *v2.CreateAccountRequest) (*v2.CreateAccountResponse, error) { + ctx, span := tracer.Start(ctx, "builder.CreateAccount") + defer span.End() + + start := b.nowFunc() + tt := tasks.CreateAccountType + l := ctxzap.Extract(ctx) + if b.accountManager == nil { + l.Error("error: connector does not have account manager configured") + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, status.Error(codes.Unimplemented, "connector does not have account manager configured") + } + + opts, err := crypto.ConvertCredentialOptions(ctx, b.clientSecret, request.GetCredentialOptions(), request.GetEncryptionConfigs()) + if err != nil { + l.Error("error: converting credential options failed", zap.Error(err)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: converting credential options failed: %w", err) + } + + result, plaintexts, annos, err := b.accountManager.CreateAccount(ctx, request.GetAccountInfo(), opts) + if err != nil { + l.Error("error: create account failed", zap.Error(err)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: create account failed: %w", err) + } + + pkem, err := crypto.NewEncryptionManager(request.GetCredentialOptions(), request.GetEncryptionConfigs()) + if err != nil { + l.Error("error: creating encryption manager failed", zap.Error(err)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: creating encryption manager failed: %w", err) + } + + var encryptedDatas []*v2.EncryptedData + for _, plaintextCredential := range plaintexts { + encryptedData, err := pkem.Encrypt(ctx, plaintextCredential) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, err + } + encryptedDatas = append(encryptedDatas, encryptedData...) + } + + rv := &v2.CreateAccountResponse{ + EncryptedData: encryptedDatas, + Annotations: annos, + } + + switch r := result.(type) { + case *v2.CreateAccountResponse_SuccessResult: + rv.Result = &v2.CreateAccountResponse_Success{Success: r} + case *v2.CreateAccountResponse_ActionRequiredResult: + rv.Result = &v2.CreateAccountResponse_ActionRequired{ActionRequired: r} + default: + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, status.Error(codes.Unimplemented, fmt.Sprintf("unknown result type: %T", result)) + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return rv, nil +} + +func (b *builder) addAccountManager(_ context.Context, typeId string, in interface{}) error { + if _, ok := in.(OldAccountManager); ok { + return fmt.Errorf("error: old account manager interface implemented for %s", typeId) + } + + if accountManager, ok := in.(AccountManagerLimited); ok { + // NOTE(kans): currently unused - but these should probably be (resource) typed + b.accountManagers[typeId] = accountManager + if b.accountManager != nil { + return fmt.Errorf("error: duplicate resource type found for account manager %s", typeId) + } + b.accountManager = accountManager + } + return nil +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/actions.go b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/actions.go new file mode 100644 index 00000000..42372379 --- /dev/null +++ b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/actions.go @@ -0,0 +1,171 @@ +package connectorbuilder + +import ( + "context" + "fmt" + + v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + "github.com/conductorone/baton-sdk/pkg/annotations" + "github.com/conductorone/baton-sdk/pkg/types/tasks" + "google.golang.org/protobuf/types/known/structpb" +) + +// CustomActionManager defines capabilities for handling custom actions. +// +// Note: RegisterActionManager is preferred for new connectors. +// +// This interface allows connectors to define and execute custom actions +// that can be triggered from Baton. +type CustomActionManager interface { + ListActionSchemas(ctx context.Context) ([]*v2.BatonActionSchema, annotations.Annotations, error) + GetActionSchema(ctx context.Context, name string) (*v2.BatonActionSchema, annotations.Annotations, error) + InvokeAction(ctx context.Context, name string, args *structpb.Struct) (string, v2.BatonActionStatus, *structpb.Struct, annotations.Annotations, error) + GetActionStatus(ctx context.Context, id string) (v2.BatonActionStatus, string, *structpb.Struct, annotations.Annotations, error) +} + +// RegisterActionManager extends ConnectorBuilder to add capabilities for registering custom actions. +// +// This is the recommended interface for implementing custom action support in new connectors. +// It provides a mechanism to register a CustomActionManager with the connector. +type RegisterActionManager interface { + ConnectorBuilder + RegisterActionManagerLimited +} + +type RegisterActionManagerLimited interface { + RegisterActionManager(ctx context.Context) (CustomActionManager, error) +} + +func (b *builder) ListActionSchemas(ctx context.Context, request *v2.ListActionSchemasRequest) (*v2.ListActionSchemasResponse, error) { + ctx, span := tracer.Start(ctx, "builder.ListActionSchemas") + defer span.End() + + start := b.nowFunc() + tt := tasks.ActionListSchemasType + if b.actionManager == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: action manager not implemented") + } + + actionSchemas, annos, err := b.actionManager.ListActionSchemas(ctx) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: listing action schemas failed: %w", err) + } + + rv := &v2.ListActionSchemasResponse{ + Schemas: actionSchemas, + Annotations: annos, + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return rv, nil +} + +func (b *builder) GetActionSchema(ctx context.Context, request *v2.GetActionSchemaRequest) (*v2.GetActionSchemaResponse, error) { + ctx, span := tracer.Start(ctx, "builder.GetActionSchema") + defer span.End() + + start := b.nowFunc() + tt := tasks.ActionGetSchemaType + if b.actionManager == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: action manager not implemented") + } + + actionSchema, annos, err := b.actionManager.GetActionSchema(ctx, request.GetName()) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: getting action schema failed: %w", err) + } + + rv := &v2.GetActionSchemaResponse{ + Schema: actionSchema, + Annotations: annos, + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return rv, nil +} + +func (b *builder) InvokeAction(ctx context.Context, request *v2.InvokeActionRequest) (*v2.InvokeActionResponse, error) { + ctx, span := tracer.Start(ctx, "builder.InvokeAction") + defer span.End() + + start := b.nowFunc() + tt := tasks.ActionInvokeType + if b.actionManager == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: action manager not implemented") + } + + id, status, resp, annos, err := b.actionManager.InvokeAction(ctx, request.GetName(), request.GetArgs()) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: invoking action failed: %w", err) + } + + rv := &v2.InvokeActionResponse{ + Id: id, + Name: request.GetName(), + Status: status, + Annotations: annos, + Response: resp, + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return rv, nil +} + +func (b *builder) GetActionStatus(ctx context.Context, request *v2.GetActionStatusRequest) (*v2.GetActionStatusResponse, error) { + ctx, span := tracer.Start(ctx, "builder.GetActionStatus") + defer span.End() + + start := b.nowFunc() + tt := tasks.ActionStatusType + if b.actionManager == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: action manager not implemented") + } + + status, name, rv, annos, err := b.actionManager.GetActionStatus(ctx, request.GetId()) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: getting action status failed: %w", err) + } + + resp := &v2.GetActionStatusResponse{ + Id: request.GetId(), + Name: name, + Status: status, + Annotations: annos, + Response: rv, + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return resp, nil +} + +func (b *builder) addActionManager(ctx context.Context, in interface{}) error { + if actionManager, ok := in.(CustomActionManager); ok { + if b.actionManager != nil { + return fmt.Errorf("error: cannot set multiple action managers") + } + b.actionManager = actionManager + } + + if registerActionManager, ok := in.(RegisterActionManagerLimited); ok { + if b.actionManager != nil { + return fmt.Errorf("error: cannot register multiple action managers") + } + actionManager, err := registerActionManager.RegisterActionManager(ctx) + if err != nil { + return fmt.Errorf("error: registering action manager failed: %w", err) + } + if actionManager == nil { + return fmt.Errorf("error: action manager is nil") + } + b.actionManager = actionManager + } + return nil +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/assets.go b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/assets.go index f6431512..d0e551c6 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/assets.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/assets.go @@ -1,5 +1,7 @@ package connectorbuilder +import v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + // FIXME(jirwin): Come back to streaming assets soon. // // const ( @@ -40,3 +42,12 @@ package connectorbuilder // } // return nil // } + +// GetAsset streams the asset to the client. +// FIXME(jirwin): Asset streaming is disabled. +func (b *builder) GetAsset(request *v2.AssetServiceGetAssetRequest, server v2.AssetService_GetAssetServer) error { + _, span := tracer.Start(server.Context(), "builderImpl.GetAsset") + defer span.End() + + return nil +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/connectorbuilder.go b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/connectorbuilder.go index 34ea77a8..b342e5b4 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/connectorbuilder.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/connectorbuilder.go @@ -14,253 +14,21 @@ import ( "go.uber.org/zap" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/structpb" - "google.golang.org/protobuf/types/known/timestamppb" v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" "github.com/conductorone/baton-sdk/pkg/annotations" "github.com/conductorone/baton-sdk/pkg/crypto" "github.com/conductorone/baton-sdk/pkg/metrics" - "github.com/conductorone/baton-sdk/pkg/pagination" "github.com/conductorone/baton-sdk/pkg/retry" "github.com/conductorone/baton-sdk/pkg/sdk" - "github.com/conductorone/baton-sdk/pkg/session" "github.com/conductorone/baton-sdk/pkg/types" + "github.com/conductorone/baton-sdk/pkg/types/sessions" "github.com/conductorone/baton-sdk/pkg/types/tasks" "github.com/conductorone/baton-sdk/pkg/uhttp" ) var tracer = otel.Tracer("baton-sdk/pkg.connectorbuilder") -// ResourceSyncer is the primary interface for connector developers to implement. -// -// It defines the core functionality for synchronizing resources, entitlements, and grants -// from external systems into Baton. Every connector must implement at least this interface -// for each resource type it supports. -// -// Extensions to this interface include: -// - ResourceProvisioner/ResourceProvisionerV2: For adding/removing access -// - ResourceManager: For creating and managing resources -// - ResourceDeleter: For deleting resources -// - AccountManager: For account provisioning operations -// - CredentialManager: For credential rotation operations. -// - ResourceTargetedSyncer: For directly getting a resource supporting targeted sync. -type ResourceSyncer interface { - ResourceType(ctx context.Context) *v2.ResourceType - List(ctx context.Context, parentResourceID *v2.ResourceId, pToken *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) - Entitlements(ctx context.Context, resource *v2.Resource, pToken *pagination.Token) ([]*v2.Entitlement, string, annotations.Annotations, error) - Grants(ctx context.Context, resource *v2.Resource, pToken *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) -} - -// ResourceProvisioner extends ResourceSyncer to add capabilities for granting and revoking access. -// -// Note: ResourceProvisionerV2 is preferred for new connectors as it provides -// enhanced grant capabilities. -// -// Implementing this interface indicates the connector supports provisioning operations -// for the associated resource type. -type ResourceProvisioner interface { - ResourceSyncer - ResourceType(ctx context.Context) *v2.ResourceType - Grant(ctx context.Context, resource *v2.Resource, entitlement *v2.Entitlement) (annotations.Annotations, error) - Revoke(ctx context.Context, grant *v2.Grant) (annotations.Annotations, error) -} - -// ResourceProvisionerV2 extends ResourceSyncer to add capabilities for granting and revoking access -// with enhanced functionality compared to ResourceProvisioner. -// -// This is the recommended interface for implementing provisioning operations in new connectors. -// It differs from ResourceProvisioner by returning a list of grants from the Grant method. -type ResourceProvisionerV2 interface { - ResourceSyncer - ResourceType(ctx context.Context) *v2.ResourceType - Grant(ctx context.Context, resource *v2.Resource, entitlement *v2.Entitlement) ([]*v2.Grant, annotations.Annotations, error) - Revoke(ctx context.Context, grant *v2.Grant) (annotations.Annotations, error) -} - -// ResourceManager extends ResourceSyncer to add capabilities for creating resources. -// -// Implementing this interface indicates the connector supports creating and deleting resources -// of the associated resource type. A ResourceManager automatically provides ResourceDeleter -// functionality. -type ResourceManager interface { - ResourceSyncer - Create(ctx context.Context, resource *v2.Resource) (*v2.Resource, annotations.Annotations, error) - ResourceDeleter -} - -// ResourceManagerV2 extends ResourceSyncer to add capabilities for creating resources. -// -// This is the recommended interface for implementing resource creation operations in new connectors. -type ResourceManagerV2 interface { - ResourceSyncer - Create(ctx context.Context, resource *v2.Resource) (*v2.Resource, annotations.Annotations, error) - ResourceDeleterV2 -} - -// ResourceDeleter extends ResourceSyncer to add capabilities for deleting resources. -// -// Implementing this interface indicates the connector supports deleting resources -// of the associated resource type. -type ResourceDeleter interface { - ResourceSyncer - Delete(ctx context.Context, resourceId *v2.ResourceId) (annotations.Annotations, error) -} - -// ResourceDeleterV2 extends ResourceSyncer to add capabilities for deleting resources. -// -// This is the recommended interface for implementing resource deletion operations in new connectors. -// It differs from ResourceDeleter by having the resource, not just the id. -type ResourceDeleterV2 interface { - ResourceSyncer - Delete(ctx context.Context, resourceId *v2.ResourceId, parentResourceID *v2.ResourceId) (annotations.Annotations, error) -} - -// ResourceTargetedSyncer extends ResourceSyncer to add capabilities for directly syncing an individual resource -// -// Implementing this interface indicates the connector supports calling "get" on a resource -// of the associated resource type. -type ResourceTargetedSyncer interface { - ResourceSyncer - Get(ctx context.Context, resourceId *v2.ResourceId, parentResourceId *v2.ResourceId) (*v2.Resource, annotations.Annotations, error) -} - -// CreateAccountResponse is a semi-opaque type returned from CreateAccount operations. -// -// This is used to communicate the result of account creation back to Baton. -type CreateAccountResponse interface { - proto.Message - GetIsCreateAccountResult() bool -} - -// AccountManager extends ResourceSyncer to add capabilities for managing user accounts. -// -// Implementing this interface indicates the connector supports creating accounts -// in the external system. A resource type should implement this interface if it -// represents users or accounts that can be provisioned. -type AccountManager interface { - ResourceSyncer - CreateAccount(ctx context.Context, - accountInfo *v2.AccountInfo, - credentialOptions *v2.LocalCredentialOptions) (CreateAccountResponse, []*v2.PlaintextData, annotations.Annotations, error) - CreateAccountCapabilityDetails(ctx context.Context) (*v2.CredentialDetailsAccountProvisioning, annotations.Annotations, error) -} - -type OldAccountManager interface { - CreateAccount(ctx context.Context, - accountInfo *v2.AccountInfo, - credentialOptions *v2.CredentialOptions) (CreateAccountResponse, []*v2.PlaintextData, annotations.Annotations, error) -} - -// CredentialManager extends ResourceSyncer to add capabilities for managing credentials. -// -// Implementing this interface indicates the connector supports rotating credentials -// for resources of the associated type. This is commonly used for user accounts -// or service accounts that have rotatable credentials. -type CredentialManager interface { - ResourceSyncer - Rotate(ctx context.Context, - resourceId *v2.ResourceId, - credentialOptions *v2.LocalCredentialOptions) ([]*v2.PlaintextData, annotations.Annotations, error) - RotateCapabilityDetails(ctx context.Context) (*v2.CredentialDetailsCredentialRotation, annotations.Annotations, error) -} - -type OldCredentialManager interface { - Rotate(ctx context.Context, - resourceId *v2.ResourceId, - credentialOptions *v2.CredentialOptions) ([]*v2.PlaintextData, annotations.Annotations, error) -} - -// Compatibility interface lets us handle both EventFeed and EventProvider the same. -type EventLister interface { - ListEvents(ctx context.Context, earliestEvent *timestamppb.Timestamp, pToken *pagination.StreamToken) ([]*v2.Event, *pagination.StreamState, annotations.Annotations, error) -} - -// Deprecated: This interface is deprecated in favor of EventProviderV2 which supports -// multiple event feeds. Implementing this interface indicates the connector can provide -// a single stream of events from the external system, enabling near real-time updates -// in Baton. New connectors should implement EventProviderV2 instead. -type EventProvider interface { - ConnectorBuilder - EventLister -} - -// NewEventProviderV2 is a new interface that allows connectors to provide multiple event feeds. -// -// This is the recommended interface for implementing event feed support in new connectors. -type EventProviderV2 interface { - ConnectorBuilder - EventFeeds(ctx context.Context) []EventFeed -} - -// EventFeed is a single stream of events from the external system. -// -// EventFeedMetadata describes this feed, and a connector can have multiple feeds. -type EventFeed interface { - EventLister - EventFeedMetadata(ctx context.Context) *v2.EventFeedMetadata -} - -type oldEventFeedWrapper struct { - feed EventLister -} - -const ( - LegacyBatonFeedId = "baton_feed_event" -) - -func (e *oldEventFeedWrapper) EventFeedMetadata(ctx context.Context) *v2.EventFeedMetadata { - return &v2.EventFeedMetadata{ - Id: LegacyBatonFeedId, - SupportedEventTypes: []v2.EventType{v2.EventType_EVENT_TYPE_UNSPECIFIED}, - } -} - -func (e *oldEventFeedWrapper) ListEvents( - ctx context.Context, - earliestEvent *timestamppb.Timestamp, - pToken *pagination.StreamToken, -) ([]*v2.Event, *pagination.StreamState, annotations.Annotations, error) { - return e.feed.ListEvents(ctx, earliestEvent, pToken) -} - -// TicketManager extends ConnectorBuilder to add capabilities for ticket management. -// -// Implementing this interface indicates the connector can integrate with an external -// ticketing system, allowing Baton to create and track tickets in that system. -type TicketManager interface { - ConnectorBuilder - GetTicket(ctx context.Context, ticketId string) (*v2.Ticket, annotations.Annotations, error) - CreateTicket(ctx context.Context, ticket *v2.Ticket, schema *v2.TicketSchema) (*v2.Ticket, annotations.Annotations, error) - GetTicketSchema(ctx context.Context, schemaID string) (*v2.TicketSchema, annotations.Annotations, error) - ListTicketSchemas(ctx context.Context, pToken *pagination.Token) ([]*v2.TicketSchema, string, annotations.Annotations, error) - BulkCreateTickets(context.Context, *v2.TicketsServiceBulkCreateTicketsRequest) (*v2.TicketsServiceBulkCreateTicketsResponse, error) - BulkGetTickets(context.Context, *v2.TicketsServiceBulkGetTicketsRequest) (*v2.TicketsServiceBulkGetTicketsResponse, error) -} - -// CustomActionManager defines capabilities for handling custom actions. -// -// Note: RegisterActionManager is preferred for new connectors. -// -// This interface allows connectors to define and execute custom actions -// that can be triggered from Baton. -type CustomActionManager interface { - ListActionSchemas(ctx context.Context) ([]*v2.BatonActionSchema, annotations.Annotations, error) - GetActionSchema(ctx context.Context, name string) (*v2.BatonActionSchema, annotations.Annotations, error) - InvokeAction(ctx context.Context, name string, args *structpb.Struct) (string, v2.BatonActionStatus, *structpb.Struct, annotations.Annotations, error) - GetActionStatus(ctx context.Context, id string) (v2.BatonActionStatus, string, *structpb.Struct, annotations.Annotations, error) -} - -// RegisterActionManager extends ConnectorBuilder to add capabilities for registering custom actions. -// -// This is the recommended interface for implementing custom action support in new connectors. -// It provides a mechanism to register a CustomActionManager with the connector. -type RegisterActionManager interface { - ConnectorBuilder - RegisterActionManager(ctx context.Context) (CustomActionManager, error) -} - // ConnectorBuilder is the foundational interface for creating Baton connectors. // // This interface defines the core capabilities required by all connectors, including @@ -269,1191 +37,274 @@ type RegisterActionManager interface { // - RegisterActionManager: For custom action support // - EventProvider: For event stream support // - TicketManager: For ticket management integration. -type ConnectorBuilder interface { - Metadata(ctx context.Context) (*v2.ConnectorMetadata, error) - Validate(ctx context.Context) (annotations.Annotations, error) - ResourceSyncers(ctx context.Context) []ResourceSyncer -} - -type builderImpl struct { - resourceBuilders map[string]ResourceSyncer - resourceProvisioners map[string]ResourceProvisioner - resourceProvisionersV2 map[string]ResourceProvisionerV2 - resourceManagers map[string]ResourceManager - resourceManagersV2 map[string]ResourceManagerV2 - resourceDeleters map[string]ResourceDeleter - resourceDeletersV2 map[string]ResourceDeleterV2 - resourceTargetedSyncers map[string]ResourceTargetedSyncer - accountManager AccountManager - actionManager CustomActionManager - credentialManagers map[string]CredentialManager - eventFeeds map[string]EventFeed - cb ConnectorBuilder - ticketManager TicketManager - ticketingEnabled bool - m *metrics.M - nowFunc func() time.Time - clientSecret *jose.JSONWebKey -} - -func (b *builderImpl) BulkCreateTickets(ctx context.Context, request *v2.TicketsServiceBulkCreateTicketsRequest) (*v2.TicketsServiceBulkCreateTicketsResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.BulkCreateTickets") - defer span.End() - - start := b.nowFunc() - tt := tasks.BulkCreateTicketsType - if b.ticketManager == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: ticket manager not implemented") - } - - reqBody := request.GetTicketRequests() - if len(reqBody) == 0 { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: request body had no items") - } - - ticketsResponse, err := b.ticketManager.BulkCreateTickets(ctx, request) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: creating tickets failed: %w", err) - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.TicketsServiceBulkCreateTicketsResponse{ - Tickets: ticketsResponse.GetTickets(), - }, nil -} - -func (b *builderImpl) BulkGetTickets(ctx context.Context, request *v2.TicketsServiceBulkGetTicketsRequest) (*v2.TicketsServiceBulkGetTicketsResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.BulkGetTickets") - defer span.End() - - start := b.nowFunc() - tt := tasks.BulkGetTicketsType - if b.ticketManager == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: ticket manager not implemented") - } - - reqBody := request.GetTicketRequests() - if len(reqBody) == 0 { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: request body had no items") - } - - ticketsResponse, err := b.ticketManager.BulkGetTickets(ctx, request) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: fetching tickets failed: %w", err) - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.TicketsServiceBulkGetTicketsResponse{ - Tickets: ticketsResponse.GetTickets(), - }, nil -} - -func (b *builderImpl) ListTicketSchemas(ctx context.Context, request *v2.TicketsServiceListTicketSchemasRequest) (*v2.TicketsServiceListTicketSchemasResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.ListTicketSchemas") - defer span.End() - - start := b.nowFunc() - tt := tasks.ListTicketSchemasType - if b.ticketManager == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: ticket manager not implemented") - } - - retryer := retry.NewRetryer(ctx, retry.RetryConfig{ - MaxAttempts: 10, - InitialDelay: 15 * time.Second, - MaxDelay: 0, - }) - - for { - out, nextPageToken, annos, err := b.ticketManager.ListTicketSchemas(ctx, &pagination.Token{ - Size: int(request.PageSize), - Token: request.PageToken, - }) - if err == nil { - if request.PageToken != "" && request.PageToken == nextPageToken { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: listing ticket schemas failed: next page token is the same as the current page token. this is most likely a connector bug") - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.TicketsServiceListTicketSchemasResponse{ - List: out, - NextPageToken: nextPageToken, - Annotations: annos, - }, nil - } - if retryer.ShouldWaitAndRetry(ctx, err) { - continue - } - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: listing ticket schemas failed: %w", err) - } -} - -func (b *builderImpl) CreateTicket(ctx context.Context, request *v2.TicketsServiceCreateTicketRequest) (*v2.TicketsServiceCreateTicketResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.CreateTicket") - defer span.End() - - start := b.nowFunc() - tt := tasks.CreateTicketType - if b.ticketManager == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: ticket manager not implemented") - } - - reqBody := request.GetRequest() - if reqBody == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: request body is nil") - } - cTicket := &v2.Ticket{ - DisplayName: reqBody.GetDisplayName(), - Description: reqBody.GetDescription(), - Status: reqBody.GetStatus(), - Labels: reqBody.GetLabels(), - CustomFields: reqBody.GetCustomFields(), - RequestedFor: reqBody.GetRequestedFor(), - } - - ticket, annos, err := b.ticketManager.CreateTicket(ctx, cTicket, request.GetSchema()) - var resp *v2.TicketsServiceCreateTicketResponse - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - if ticket != nil { - resp = &v2.TicketsServiceCreateTicketResponse{ - Ticket: ticket, - Annotations: annos, - } - } - return resp, fmt.Errorf("error: creating ticket failed: %w", err) - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.TicketsServiceCreateTicketResponse{ - Ticket: ticket, - Annotations: annos, - }, nil -} - -func (b *builderImpl) GetTicket(ctx context.Context, request *v2.TicketsServiceGetTicketRequest) (*v2.TicketsServiceGetTicketResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.GetTicket") - defer span.End() - - start := b.nowFunc() - tt := tasks.GetTicketType - if b.ticketManager == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: ticket manager not implemented") - } - - var resp *v2.TicketsServiceGetTicketResponse - ticket, annos, err := b.ticketManager.GetTicket(ctx, request.GetId()) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - if ticket != nil { - resp = &v2.TicketsServiceGetTicketResponse{ - Ticket: ticket, - Annotations: annos, - } - } - return resp, fmt.Errorf("error: getting ticket failed: %w", err) - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.TicketsServiceGetTicketResponse{ - Ticket: ticket, - Annotations: annos, - }, nil -} - -func (b *builderImpl) GetTicketSchema(ctx context.Context, request *v2.TicketsServiceGetTicketSchemaRequest) (*v2.TicketsServiceGetTicketSchemaResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.GetTicketSchema") - defer span.End() - - start := b.nowFunc() - tt := tasks.GetTicketSchemaType - if b.ticketManager == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: ticket manager not implemented") - } - - ticketSchema, annos, err := b.ticketManager.GetTicketSchema(ctx, request.GetId()) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: getting ticket metadata failed: %w", err) - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.TicketsServiceGetTicketSchemaResponse{ - Schema: ticketSchema, - Annotations: annos, - }, nil -} - -// NewConnector creates a new ConnectorServer for a new resource. -func NewConnector(ctx context.Context, in interface{}, opts ...Opt) (types.ConnectorServer, error) { - switch c := in.(type) { - case ConnectorBuilder: - clientSecretValue := ctx.Value(crypto.ContextClientSecretKey) - clientSecretJWK, _ := clientSecretValue.(*jose.JSONWebKey) - - ret := &builderImpl{ - resourceBuilders: make(map[string]ResourceSyncer), - resourceProvisioners: make(map[string]ResourceProvisioner), - resourceProvisionersV2: make(map[string]ResourceProvisionerV2), - resourceManagers: make(map[string]ResourceManager), - resourceManagersV2: make(map[string]ResourceManagerV2), - resourceDeleters: make(map[string]ResourceDeleter), - resourceDeletersV2: make(map[string]ResourceDeleterV2), - resourceTargetedSyncers: make(map[string]ResourceTargetedSyncer), - accountManager: nil, - actionManager: nil, - credentialManagers: make(map[string]CredentialManager), - eventFeeds: make(map[string]EventFeed), - cb: c, - ticketManager: nil, - nowFunc: time.Now, - clientSecret: clientSecretJWK, - } - - err := ret.options(opts...) - if err != nil { - return nil, err - } - - if ret.m == nil { - ret.m = metrics.New(metrics.NewNoOpHandler(ctx)) - } - - if b, ok := c.(EventProviderV2); ok { - for _, ef := range b.EventFeeds(ctx) { - feedData := ef.EventFeedMetadata(ctx) - if feedData == nil { - return nil, fmt.Errorf("error: event feed metadata is nil") - } - if err := feedData.Validate(); err != nil { - return nil, fmt.Errorf("error: event feed metadata for %s is invalid: %w", feedData.Id, err) - } - if _, ok := ret.eventFeeds[feedData.Id]; ok { - return nil, fmt.Errorf("error: duplicate event feed id found: %s", feedData.Id) - } - ret.eventFeeds[feedData.Id] = ef - } - } - - if b, ok := c.(EventProvider); ok { - // Register the legacy Baton feed as a v2 event feed - // implementing both v1 and v2 event feeds is not supported. - if len(ret.eventFeeds) != 0 { - return nil, fmt.Errorf("error: using legacy event feed is not supported when using EventProviderV2") - } - ret.eventFeeds[LegacyBatonFeedId] = &oldEventFeedWrapper{ - feed: b, - } - } - - if ticketManager, ok := c.(TicketManager); ok { - if ret.ticketManager != nil { - return nil, fmt.Errorf("error: cannot set multiple ticket managers") - } - ret.ticketManager = ticketManager - } - - if actionManager, ok := c.(CustomActionManager); ok { - if ret.actionManager != nil { - return nil, fmt.Errorf("error: cannot set multiple action managers") - } - ret.actionManager = actionManager - } - - if registerActionManager, ok := c.(RegisterActionManager); ok { - if ret.actionManager != nil { - return nil, fmt.Errorf("error: cannot register multiple action managers") - } - actionManager, err := registerActionManager.RegisterActionManager(ctx) - if err != nil { - return nil, fmt.Errorf("error: registering action manager failed: %w", err) - } - if actionManager == nil { - return nil, fmt.Errorf("error: action manager is nil") - } - ret.actionManager = actionManager - } - - for _, rb := range c.ResourceSyncers(ctx) { - rType := rb.ResourceType(ctx) - if _, ok := ret.resourceBuilders[rType.Id]; ok { - return nil, fmt.Errorf("error: duplicate resource type found for resource builder %s", rType.Id) - } - ret.resourceBuilders[rType.Id] = rb - - if err := validateProvisionerVersion(ctx, rb); err != nil { - return nil, err - } - if provisioner, ok := rb.(ResourceProvisioner); ok { - if _, ok := ret.resourceProvisioners[rType.Id]; ok { - return nil, fmt.Errorf("error: duplicate resource type found for resource provisioner %s", rType.Id) - } - ret.resourceProvisioners[rType.Id] = provisioner - } - if provisioner, ok := rb.(ResourceProvisionerV2); ok { - if _, ok := ret.resourceProvisionersV2[rType.Id]; ok { - return nil, fmt.Errorf("error: duplicate resource type found for resource provisioner v2 %s", rType.Id) - } - ret.resourceProvisionersV2[rType.Id] = provisioner - } - if targetedSyncer, ok := rb.(ResourceTargetedSyncer); ok { - if _, ok := ret.resourceTargetedSyncers[rType.Id]; ok { - return nil, fmt.Errorf("error: duplicate resource type found for resource targeted syncer %s", rType.Id) - } - ret.resourceTargetedSyncers[rType.Id] = targetedSyncer - } - - if resourceManager, ok := rb.(ResourceManager); ok { - if _, ok := ret.resourceManagers[rType.Id]; ok { - return nil, fmt.Errorf("error: duplicate resource type found for resource manager %s", rType.Id) - } - ret.resourceManagers[rType.Id] = resourceManager - // Support DeleteResourceV2 if connector implements both Create and Delete - if _, ok := ret.resourceDeleters[rType.Id]; ok { - // This should never happen - return nil, fmt.Errorf("error: duplicate resource type found for resource deleter %s", rType.Id) - } - ret.resourceDeleters[rType.Id] = resourceManager - } else { - if resourceDeleter, ok := rb.(ResourceDeleter); ok { - if _, ok := ret.resourceDeleters[rType.Id]; ok { - return nil, fmt.Errorf("error: duplicate resource type found for resource deleter %s", rType.Id) - } - ret.resourceDeleters[rType.Id] = resourceDeleter - } - } - - if resourceManager, ok := rb.(ResourceManagerV2); ok { - if _, ok := ret.resourceManagersV2[rType.Id]; ok { - return nil, fmt.Errorf("error: duplicate resource type found for resource managerV2 %s", rType.Id) - } - ret.resourceManagersV2[rType.Id] = resourceManager - // Support DeleteResourceV2 if connector implements both Create and Delete - if _, ok := ret.resourceDeletersV2[rType.Id]; ok { - // This should never happen - return nil, fmt.Errorf("error: duplicate resource type found for resource deleterV2 %s", rType.Id) - } - ret.resourceDeletersV2[rType.Id] = resourceManager - } else { - if resourceDeleter, ok := rb.(ResourceDeleterV2); ok { - if _, ok := ret.resourceDeletersV2[rType.Id]; ok { - return nil, fmt.Errorf("error: duplicate resource type found for resource deleterV2 %s", rType.Id) - } - ret.resourceDeletersV2[rType.Id] = resourceDeleter - } - } - - if _, ok := rb.(OldAccountManager); ok { - return nil, fmt.Errorf("error: old account manager interface implemented for %s", rType.Id) - } - - if accountManager, ok := rb.(AccountManager); ok { - if ret.accountManager != nil { - return nil, fmt.Errorf("error: duplicate resource type found for account manager %s", rType.Id) - } - ret.accountManager = accountManager - } - - if _, ok := rb.(OldCredentialManager); ok { - return nil, fmt.Errorf("error: old credential manager interface implemented for %s", rType.Id) - } - - if credentialManagers, ok := rb.(CredentialManager); ok { - if _, ok := ret.credentialManagers[rType.Id]; ok { - return nil, fmt.Errorf("error: duplicate resource type found for credential manager %s", rType.Id) - } - ret.credentialManagers[rType.Id] = credentialManagers - } - } - return ret, nil - - case types.ConnectorServer: - return c, nil - - default: - return nil, fmt.Errorf("input was not a ConnectorBuilder or a ConnectorServer") - } -} - -type Opt func(b *builderImpl) error - -func WithTicketingEnabled() Opt { - return func(b *builderImpl) error { - if _, ok := b.cb.(TicketManager); ok { - b.ticketingEnabled = true - return nil - } - return errors.New("external ticketing not supported") - } +type MetadataProvider interface { + Metadata(ctx context.Context) (*v2.ConnectorMetadata, error) } -func WithMetricsHandler(h metrics.Handler) Opt { - return func(b *builderImpl) error { - b.m = metrics.New(h) - return nil - } -} - -func (b *builderImpl) options(opts ...Opt) error { - for _, opt := range opts { - if err := opt(b); err != nil { - return err - } - } - - return nil -} - -func validateProvisionerVersion(ctx context.Context, p ResourceSyncer) error { - _, ok := p.(ResourceProvisioner) - _, okV2 := p.(ResourceProvisionerV2) - - if ok && okV2 { - return fmt.Errorf("error: resource type %s implements both ResourceProvisioner and ResourceProvisionerV2", p.ResourceType(ctx).Id) - } - return nil -} - -// ListResourceTypes lists all available resource types. -func (b *builderImpl) ListResourceTypes( - ctx context.Context, - request *v2.ResourceTypesServiceListResourceTypesRequest, -) (*v2.ResourceTypesServiceListResourceTypesResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.ListResourceTypes") - defer span.End() - - start := b.nowFunc() - tt := tasks.ListResourceTypesType - var out []*v2.ResourceType - - if len(b.resourceBuilders) == 0 { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: no resource builders found") - } - - for _, rb := range b.resourceBuilders { - out = append(out, rb.ResourceType(ctx)) - } - - if len(out) == 0 { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: no resource types found") - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.ResourceTypesServiceListResourceTypesResponse{List: out}, nil -} - -// ListResources returns all available resources for a given resource type ID. -func (b *builderImpl) ListResources(ctx context.Context, request *v2.ResourcesServiceListResourcesRequest) (*v2.ResourcesServiceListResourcesResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.ListResources") - defer span.End() - - start := b.nowFunc() - tt := tasks.ListResourcesType - rb, ok := b.resourceBuilders[request.ResourceTypeId] - if !ok { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: list resources with unknown resource type %s", request.ResourceTypeId) - } - out, nextPageToken, annos, err := rb.List(ctx, request.ParentResourceId, &pagination.Token{ - Size: int(request.PageSize), - Token: request.PageToken, - }) - resp := &v2.ResourcesServiceListResourcesResponse{ - List: out, - NextPageToken: nextPageToken, - Annotations: annos, - } - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return resp, fmt.Errorf("error: listing resources failed: %w", err) - } - if request.PageToken != "" && request.PageToken == nextPageToken { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return resp, fmt.Errorf("error: listing resources failed: next page token is the same as the current page token. this is most likely a connector bug") - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return resp, nil -} - -func (b *builderImpl) GetResource(ctx context.Context, request *v2.ResourceGetterServiceGetResourceRequest) (*v2.ResourceGetterServiceGetResourceResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.GetResource") - defer span.End() - - start := b.nowFunc() - tt := tasks.GetResourceType - resourceType := request.GetResourceId().GetResourceType() - rb, ok := b.resourceTargetedSyncers[resourceType] - if !ok { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, status.Errorf(codes.Unimplemented, "error: get resource with unknown resource type %s", resourceType) - } - - resource, annos, err := rb.Get(ctx, request.GetResourceId(), request.GetParentResourceId()) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: get resource failed: %w", err) - } - if resource == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, status.Error(codes.NotFound, "error: get resource returned nil") - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.ResourceGetterServiceGetResourceResponse{ - Resource: resource, - Annotations: annos, - }, nil -} - -// ListEntitlements returns all the entitlements for a given resource. -func (b *builderImpl) ListEntitlements(ctx context.Context, request *v2.EntitlementsServiceListEntitlementsRequest) (*v2.EntitlementsServiceListEntitlementsResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.ListEntitlements") - defer span.End() - - start := b.nowFunc() - tt := tasks.ListEntitlementsType - rb, ok := b.resourceBuilders[request.Resource.Id.ResourceType] - if !ok { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: list entitlements with unknown resource type %s", request.Resource.Id.ResourceType) - } - - out, nextPageToken, annos, err := rb.Entitlements(ctx, request.Resource, &pagination.Token{ - Size: int(request.PageSize), - Token: request.PageToken, - }) - resp := &v2.EntitlementsServiceListEntitlementsResponse{ - List: out, - NextPageToken: nextPageToken, - Annotations: annos, - } - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return resp, fmt.Errorf("error: listing entitlements failed: %w", err) - } - if request.PageToken != "" && request.PageToken == nextPageToken { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return resp, fmt.Errorf("error: listing entitlements failed: next page token is the same as the current page token. this is most likely a connector bug") - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return resp, nil -} - -// ListGrants lists all the grants for a given resource. -func (b *builderImpl) ListGrants(ctx context.Context, request *v2.GrantsServiceListGrantsRequest) (*v2.GrantsServiceListGrantsResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.ListGrants") - defer span.End() - - start := b.nowFunc() - tt := tasks.ListGrantsType - rid := request.Resource.Id - rb, ok := b.resourceBuilders[rid.ResourceType] - if !ok { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: list entitlements with unknown resource type %s", rid.ResourceType) - } - - out, nextPageToken, annos, err := rb.Grants(ctx, request.Resource, &pagination.Token{ - Size: int(request.PageSize), - Token: request.PageToken, - }) - - resp := &v2.GrantsServiceListGrantsResponse{ - List: out, - NextPageToken: nextPageToken, - Annotations: annos, - } - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return resp, fmt.Errorf("error: listing grants for resource %s/%s failed: %w", rid.ResourceType, rid.Resource, err) - } - if request.PageToken != "" && request.PageToken == nextPageToken { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return resp, fmt.Errorf("error: listing grants for resource %s/%s failed: next page token is the same as the current page token. this is most likely a connector bug", - rid.ResourceType, - rid.Resource) - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return resp, nil -} - -// GetMetadata gets all metadata for a connector. -func (b *builderImpl) GetMetadata(ctx context.Context, request *v2.ConnectorServiceGetMetadataRequest) (*v2.ConnectorServiceGetMetadataResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.GetMetadata") - defer span.End() - - start := b.nowFunc() - tt := tasks.GetMetadataType - md, err := b.cb.Metadata(ctx) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, err - } - - md.Capabilities, err = getCapabilities(ctx, b) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, err - } - - annos := annotations.Annotations(md.Annotations) - if b.ticketManager != nil { - annos.Append(&v2.ExternalTicketSettings{Enabled: b.ticketingEnabled}) - } - md.Annotations = annos - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.ConnectorServiceGetMetadataResponse{Metadata: md}, nil -} - -func validateCapabilityDetails(ctx context.Context, credDetails *v2.CredentialDetails) error { - if credDetails.CapabilityAccountProvisioning != nil { - // Ensure that the preferred option is included and is part of the supported options - if credDetails.CapabilityAccountProvisioning.PreferredCredentialOption == v2.CapabilityDetailCredentialOption_CAPABILITY_DETAIL_CREDENTIAL_OPTION_UNSPECIFIED { - return status.Error(codes.InvalidArgument, "error: preferred credential creation option is not set") - } - if !slices.Contains(credDetails.CapabilityAccountProvisioning.SupportedCredentialOptions, credDetails.CapabilityAccountProvisioning.PreferredCredentialOption) { - return status.Error(codes.InvalidArgument, "error: preferred credential creation option is not part of the supported options") - } - } - - if credDetails.CapabilityCredentialRotation != nil { - // Ensure that the preferred option is included and is part of the supported options - if credDetails.CapabilityCredentialRotation.PreferredCredentialOption == v2.CapabilityDetailCredentialOption_CAPABILITY_DETAIL_CREDENTIAL_OPTION_UNSPECIFIED { - return status.Error(codes.InvalidArgument, "error: preferred credential rotation option is not set") - } - if !slices.Contains(credDetails.CapabilityCredentialRotation.SupportedCredentialOptions, credDetails.CapabilityCredentialRotation.PreferredCredentialOption) { - return status.Error(codes.InvalidArgument, "error: preferred credential rotation option is not part of the supported options") - } - } - - return nil -} - -func getCredentialDetails(ctx context.Context, b *builderImpl) (*v2.CredentialDetails, error) { - l := ctxzap.Extract(ctx) - rv := &v2.CredentialDetails{} - - for _, rb := range b.resourceBuilders { - if am, ok := rb.(AccountManager); ok { - accountProvisioningCapabilityDetails, _, err := am.CreateAccountCapabilityDetails(ctx) - if err != nil { - l.Error("error: getting account provisioning details", zap.Error(err)) - return nil, fmt.Errorf("error: getting account provisioning details: %w", err) - } - rv.CapabilityAccountProvisioning = accountProvisioningCapabilityDetails - } - - if cm, ok := rb.(CredentialManager); ok { - credentialRotationCapabilityDetails, _, err := cm.RotateCapabilityDetails(ctx) - if err != nil { - l.Error("error: getting credential management details", zap.Error(err)) - return nil, fmt.Errorf("error: getting credential management details: %w", err) - } - rv.CapabilityCredentialRotation = credentialRotationCapabilityDetails - } - } - - err := validateCapabilityDetails(ctx, rv) - if err != nil { - return nil, fmt.Errorf("error: validating capability details: %w", err) - } - return rv, nil -} - -// getCapabilities gets all capabilities for a connector. -func getCapabilities(ctx context.Context, b *builderImpl) (*v2.ConnectorCapabilities, error) { - connectorCaps := make(map[v2.Capability]struct{}) - resourceTypeCapabilities := []*v2.ResourceTypeCapability{} - for _, rb := range b.resourceBuilders { - resourceTypeCapability := &v2.ResourceTypeCapability{ - ResourceType: rb.ResourceType(ctx), - // Currently by default all resource types support sync. - Capabilities: []v2.Capability{v2.Capability_CAPABILITY_SYNC}, - } - connectorCaps[v2.Capability_CAPABILITY_SYNC] = struct{}{} - if _, ok := rb.(ResourceTargetedSyncer); ok { - resourceTypeCapability.Capabilities = append(resourceTypeCapability.Capabilities, v2.Capability_CAPABILITY_TARGETED_SYNC) - connectorCaps[v2.Capability_CAPABILITY_TARGETED_SYNC] = struct{}{} - } - if _, ok := rb.(ResourceProvisioner); ok { - resourceTypeCapability.Capabilities = append(resourceTypeCapability.Capabilities, v2.Capability_CAPABILITY_PROVISION) - connectorCaps[v2.Capability_CAPABILITY_PROVISION] = struct{}{} - } else if _, ok = rb.(ResourceProvisionerV2); ok { - resourceTypeCapability.Capabilities = append(resourceTypeCapability.Capabilities, v2.Capability_CAPABILITY_PROVISION) - connectorCaps[v2.Capability_CAPABILITY_PROVISION] = struct{}{} - } - if _, ok := rb.(AccountManager); ok { - resourceTypeCapability.Capabilities = append(resourceTypeCapability.Capabilities, v2.Capability_CAPABILITY_ACCOUNT_PROVISIONING) - connectorCaps[v2.Capability_CAPABILITY_ACCOUNT_PROVISIONING] = struct{}{} - } - - if _, ok := rb.(CredentialManager); ok { - resourceTypeCapability.Capabilities = append(resourceTypeCapability.Capabilities, v2.Capability_CAPABILITY_CREDENTIAL_ROTATION) - connectorCaps[v2.Capability_CAPABILITY_CREDENTIAL_ROTATION] = struct{}{} - } - - if _, ok := rb.(ResourceManager); ok { - resourceTypeCapability.Capabilities = append(resourceTypeCapability.Capabilities, v2.Capability_CAPABILITY_RESOURCE_CREATE, v2.Capability_CAPABILITY_RESOURCE_DELETE) - connectorCaps[v2.Capability_CAPABILITY_RESOURCE_CREATE] = struct{}{} - connectorCaps[v2.Capability_CAPABILITY_RESOURCE_DELETE] = struct{}{} - } else if _, ok := rb.(ResourceDeleter); ok { - resourceTypeCapability.Capabilities = append(resourceTypeCapability.Capabilities, v2.Capability_CAPABILITY_RESOURCE_DELETE) - connectorCaps[v2.Capability_CAPABILITY_RESOURCE_DELETE] = struct{}{} - } - - if _, ok := rb.(ResourceManagerV2); ok { - resourceTypeCapability.Capabilities = append(resourceTypeCapability.Capabilities, v2.Capability_CAPABILITY_RESOURCE_CREATE, v2.Capability_CAPABILITY_RESOURCE_DELETE) - connectorCaps[v2.Capability_CAPABILITY_RESOURCE_CREATE] = struct{}{} - connectorCaps[v2.Capability_CAPABILITY_RESOURCE_DELETE] = struct{}{} - } else if _, ok := rb.(ResourceDeleterV2); ok { - resourceTypeCapability.Capabilities = append(resourceTypeCapability.Capabilities, v2.Capability_CAPABILITY_RESOURCE_DELETE) - connectorCaps[v2.Capability_CAPABILITY_RESOURCE_DELETE] = struct{}{} - } - - resourceTypeCapabilities = append(resourceTypeCapabilities, resourceTypeCapability) - } - sort.Slice(resourceTypeCapabilities, func(i, j int) bool { - return resourceTypeCapabilities[i].ResourceType.GetId() < resourceTypeCapabilities[j].ResourceType.GetId() - }) - - if len(b.eventFeeds) > 0 { - connectorCaps[v2.Capability_CAPABILITY_EVENT_FEED_V2] = struct{}{} - } - - if b.ticketManager != nil { - connectorCaps[v2.Capability_CAPABILITY_TICKETING] = struct{}{} - } - - if b.actionManager != nil { - connectorCaps[v2.Capability_CAPABILITY_ACTIONS] = struct{}{} - } - - var caps []v2.Capability - for c := range connectorCaps { - caps = append(caps, c) - } - slices.Sort(caps) - - credDetails, err := getCredentialDetails(ctx, b) - if err != nil { - return nil, err - } - - return &v2.ConnectorCapabilities{ - ResourceTypeCapabilities: resourceTypeCapabilities, - ConnectorCapabilities: caps, - CredentialDetails: credDetails, - }, nil -} - -// Validate validates the connector. -func (b *builderImpl) Validate(ctx context.Context, request *v2.ConnectorServiceValidateRequest) (*v2.ConnectorServiceValidateResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.Validate") - defer span.End() - - retryer := retry.NewRetryer(ctx, retry.RetryConfig{ - MaxAttempts: 5, - InitialDelay: 1 * time.Second, - MaxDelay: 0, - }) - - for { - annos, err := b.cb.Validate(ctx) - if err == nil { - return &v2.ConnectorServiceValidateResponse{ - Annotations: annos, - SdkVersion: sdk.Version, - }, nil - } - - if retryer.ShouldWaitAndRetry(ctx, err) { - continue - } - - return nil, fmt.Errorf("validate failed: %w", err) - } -} - -func (b *builderImpl) Grant(ctx context.Context, request *v2.GrantManagerServiceGrantRequest) (*v2.GrantManagerServiceGrantResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.Grant") - defer span.End() - - start := b.nowFunc() - tt := tasks.GrantType - l := ctxzap.Extract(ctx) - - rt := request.Entitlement.Resource.Id.ResourceType - - retryer := retry.NewRetryer(ctx, retry.RetryConfig{ - MaxAttempts: 3, - InitialDelay: 15 * time.Second, - MaxDelay: 60 * time.Second, - }) - - var grantFunc func(ctx context.Context, principal *v2.Resource, entitlement *v2.Entitlement) ([]*v2.Grant, annotations.Annotations, error) - provisioner, ok := b.resourceProvisioners[rt] - if ok { - grantFunc = func(ctx context.Context, principal *v2.Resource, entitlement *v2.Entitlement) ([]*v2.Grant, annotations.Annotations, error) { - annos, err := provisioner.Grant(ctx, principal, entitlement) - if err != nil { - return nil, annos, err - } - return nil, annos, nil - } - } - provisionerV2, ok := b.resourceProvisionersV2[rt] - if ok { - grantFunc = provisionerV2.Grant - } - - if grantFunc == nil { - l.Error("error: resource type does not have provisioner configured", zap.String("resource_type", rt)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: resource type does not have provisioner configured") - } - - for { - grants, annos, err := grantFunc(ctx, request.Principal, request.Entitlement) - if err == nil { - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.GrantManagerServiceGrantResponse{Annotations: annos, Grants: grants}, nil - } - if retryer.ShouldWaitAndRetry(ctx, err) { - continue - } - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("grant failed: %w", err) - } -} - -func (b *builderImpl) Revoke(ctx context.Context, request *v2.GrantManagerServiceRevokeRequest) (*v2.GrantManagerServiceRevokeResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.Revoke") - defer span.End() - - start := b.nowFunc() - tt := tasks.RevokeType - - l := ctxzap.Extract(ctx) - - rt := request.Grant.Entitlement.Resource.Id.ResourceType - - retryer := retry.NewRetryer(ctx, retry.RetryConfig{ - MaxAttempts: 3, - InitialDelay: 15 * time.Second, - MaxDelay: 60 * time.Second, - }) - - var revokeFunc func(ctx context.Context, grant *v2.Grant) (annotations.Annotations, error) - provisioner, ok := b.resourceProvisioners[rt] - if ok { - revokeFunc = provisioner.Revoke - } - provisionerV2, ok := b.resourceProvisionersV2[rt] - if ok { - revokeFunc = provisionerV2.Revoke - } - - if revokeFunc == nil { - l.Error("error: resource type does not have provisioner configured", zap.String("resource_type", rt)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: resource type does not have provisioner configured") - } - - for { - annos, err := revokeFunc(ctx, request.Grant) - if err == nil { - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.GrantManagerServiceRevokeResponse{Annotations: annos}, nil - } - if retryer.ShouldWaitAndRetry(ctx, err) { - continue - } - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("revoke failed: %w", err) - } -} - -// GetAsset streams the asset to the client. -// FIXME(jirwin): Asset streaming is disabled. -func (b *builderImpl) GetAsset(request *v2.AssetServiceGetAssetRequest, server v2.AssetService_GetAssetServer) error { - _, span := tracer.Start(server.Context(), "builderImpl.GetAsset") - defer span.End() - - return nil -} - -func (b *builderImpl) ListEventFeeds(ctx context.Context, request *v2.ListEventFeedsRequest) (*v2.ListEventFeedsResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.ListEventFeeds") - defer span.End() - - start := b.nowFunc() - tt := tasks.ListEventFeedsType - - feeds := make([]*v2.EventFeedMetadata, 0, len(b.eventFeeds)) - - for _, feed := range b.eventFeeds { - feeds = append(feeds, feed.EventFeedMetadata(ctx)) - } +type ValidateProvider interface { + Validate(ctx context.Context) (annotations.Annotations, error) +} - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.ListEventFeedsResponse{ - List: feeds, - }, nil +type ConnectorBuilder interface { + MetadataProvider + ValidateProvider + ResourceSyncers(ctx context.Context) []ResourceSyncer } -func (b *builderImpl) ListEvents(ctx context.Context, request *v2.ListEventsRequest) (*v2.ListEventsResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.ListEvents") - defer span.End() +type ConnectorBuilderV2 interface { + MetadataProvider + ValidateProvider + ResourceSyncers(ctx context.Context) []ResourceSyncerV2 +} - start := b.nowFunc() - feedId := request.GetEventFeedId() +type builder struct { + ticketingEnabled bool + m *metrics.M + nowFunc func() time.Time + clientSecret *jose.JSONWebKey + sessionStore sessions.SessionStore + metadataProvider MetadataProvider + validateProvider ValidateProvider + ticketManager TicketManagerLimited + accountManager AccountManagerLimited + actionManager CustomActionManager + resourceSyncers map[string]ResourceSyncerV2 + resourceProvisioners map[string]ResourceProvisionerV2Limited + resourceManagers map[string]ResourceManagerV2Limited + resourceDeleters map[string]ResourceDeleterV2Limited + resourceTargetedSyncers map[string]ResourceTargetedSyncerLimited + credentialManagers map[string]CredentialManagerLimited + eventFeeds map[string]EventFeed + accountManagers map[string]AccountManagerLimited // NOTE(kans): currently unused +} - // If no feedId is provided, use the legacy Baton feed Id - if feedId == "" { - feedId = LegacyBatonFeedId +// NewConnector creates a new ConnectorServer for a new resource. +func NewConnector(ctx context.Context, in interface{}, opts ...Opt) (types.ConnectorServer, error) { + if in == nil { + return nil, fmt.Errorf("input cannot be nil") } - feed, ok := b.eventFeeds[feedId] - if !ok { - return nil, status.Errorf(codes.NotFound, "error: event feed not found") + switch t := in.(type) { + case types.ConnectorServer: + // its likely nothing uses this code path anymore + return t, nil + case ConnectorBuilder, ConnectorBuilderV2: + default: + return nil, fmt.Errorf("input is not a ConnectorServer, ConnectorBuilder, or ConnectorBuilderV2") + } + + clientSecretValue := ctx.Value(crypto.ContextClientSecretKey) + clientSecretJWK, _ := clientSecretValue.(*jose.JSONWebKey) + + b := &builder{ + metadataProvider: nil, + validateProvider: nil, + ticketManager: nil, + accountManager: nil, + actionManager: nil, + nowFunc: time.Now, + clientSecret: clientSecretJWK, + resourceSyncers: make(map[string]ResourceSyncerV2), + resourceProvisioners: make(map[string]ResourceProvisionerV2Limited), + resourceManagers: make(map[string]ResourceManagerV2Limited), + resourceDeleters: make(map[string]ResourceDeleterV2Limited), + resourceTargetedSyncers: make(map[string]ResourceTargetedSyncerLimited), + credentialManagers: make(map[string]CredentialManagerLimited), + eventFeeds: make(map[string]EventFeed), + accountManagers: make(map[string]AccountManagerLimited), + } + + // WithTicketingEnabled checks for the ticketManager + if err := b.addTicketManager(ctx, in); err != nil { + return nil, err } - tt := tasks.ListEventsType - events, streamState, annotations, err := feed.ListEvents(ctx, request.StartAt, &pagination.StreamToken{ - Size: int(request.PageSize), - Cursor: request.Cursor, - }) + err := b.options(opts...) if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: listing events failed: %w", err) + return nil, err } - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.ListEventsResponse{ - Events: events, - Cursor: streamState.Cursor, - HasMore: streamState.HasMore, - Annotations: annotations, - }, nil -} -func (b *builderImpl) CreateResource(ctx context.Context, request *v2.CreateResourceRequest) (*v2.CreateResourceResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.CreateResource") - defer span.End() + if b.m == nil { + b.m = metrics.New(metrics.NewNoOpHandler(ctx)) + } - start := b.nowFunc() - tt := tasks.CreateResourceType - l := ctxzap.Extract(ctx) - rt := request.GetResource().GetId().GetResourceType() + if err := b.addConnectorBuilderProviders(ctx, in); err != nil { + return nil, err + } - var manager interface { - Create(ctx context.Context, resource *v2.Resource) (*v2.Resource, annotations.Annotations, error) + if err := b.addEventFeed(ctx, in); err != nil { + return nil, err } - manager, ok := b.resourceManagersV2[rt] - if !ok { - manager, ok = b.resourceManagers[rt] + if err := b.addActionManager(ctx, in); err != nil { + return nil, err } - if ok { - resource, annos, err := manager.Create(ctx, request.Resource) - if err != nil { - l.Error("error: create resource failed", zap.Error(err)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: create resource failed: %w", err) + addResourceType := func(ctx context.Context, rType string, rs interface{}) error { + if err := b.addResourceSyncers(ctx, rType, rs); err != nil { + return err } - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.CreateResourceResponse{Created: resource, Annotations: annos}, nil - } - l.Error("error: resource type does not have resource Create() configured", zap.String("resource_type", rt)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, status.Error(codes.Unimplemented, fmt.Sprintf("resource type %s does not have resource Create() configured", rt)) -} -func (b *builderImpl) DeleteResource(ctx context.Context, request *v2.DeleteResourceRequest) (*v2.DeleteResourceResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.DeleteResource") - defer span.End() + if err := b.addProvisioner(ctx, rType, rs); err != nil { + return err + } - start := b.nowFunc() - tt := tasks.DeleteResourceType + if err := b.addTargetedSyncer(ctx, rType, rs); err != nil { + return err + } - l := ctxzap.Extract(ctx) - rt := request.GetResourceId().GetResourceType() - var rsDeleter ResourceDeleter - var rsDeleterV2 ResourceDeleterV2 - var ok bool - - rsDeleterV2, ok = b.resourceManagersV2[rt] - if !ok { - rsDeleterV2, ok = b.resourceDeletersV2[rt] - } + if err := b.addResourceManager(ctx, rType, rs); err != nil { + return err + } - if ok { - annos, err := rsDeleterV2.Delete(ctx, request.ResourceId, request.ParentResourceId) - if err != nil { - l.Error("error: deleteV2 resource failed", zap.Error(err)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: delete resource failed: %w", err) + if err := b.addAccountManager(ctx, rType, rs); err != nil { + return err + } + + if err := b.addCredentialManager(ctx, rType, rs); err != nil { + return err } - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.DeleteResourceResponse{Annotations: annos}, nil + + return nil } - rsDeleter, ok = b.resourceManagers[rt] - if !ok { - rsDeleter, ok = b.resourceDeleters[rt] + if cb, ok := in.(ConnectorBuilder); ok { + for _, rb := range cb.ResourceSyncers(ctx) { + rType := rb.ResourceType(ctx) + if err := addResourceType(ctx, rType.Id, rb); err != nil { + return nil, err + } + } + return b, nil } - if ok { - annos, err := rsDeleter.Delete(ctx, request.GetResourceId()) - if err != nil { - l.Error("error: delete resource failed", zap.Error(err)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: delete resource failed: %w", err) + + if cb2, ok := in.(ConnectorBuilderV2); ok { + for _, rb := range cb2.ResourceSyncers(ctx) { + rType := rb.ResourceType(ctx) + if err := addResourceType(ctx, rType.Id, rb); err != nil { + return nil, err + } } - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.DeleteResourceResponse{Annotations: annos}, nil + return b, nil } - l.Error("error: resource type does not have resource Delete() configured", zap.String("resource_type", rt)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, status.Error(codes.Unimplemented, fmt.Sprintf("resource type %s does not have resource Delete() configured", rt)) + return nil, fmt.Errorf("input is not a ConnectorBuilder or a ConnectorBuilderV2") } -func (b *builderImpl) DeleteResourceV2(ctx context.Context, request *v2.DeleteResourceV2Request) (*v2.DeleteResourceV2Response, error) { - ctx, span := tracer.Start(ctx, "builderImpl.DeleteResourceV2") - defer span.End() - - start := b.nowFunc() - tt := tasks.DeleteResourceType +type Opt func(b *builder) error - l := ctxzap.Extract(ctx) - rt := request.GetResourceId().GetResourceType() - var rsDeleter ResourceDeleter - var rsDeleterV2 ResourceDeleterV2 - var ok bool - - rsDeleterV2, ok = b.resourceManagersV2[rt] - if !ok { - rsDeleterV2, ok = b.resourceDeletersV2[rt] +func WithTicketingEnabled() Opt { + return func(b *builder) error { + if b.ticketManager == nil { + return errors.New("external ticketing not supported") + } + b.ticketingEnabled = true + return nil } +} - if ok { - annos, err := rsDeleterV2.Delete(ctx, request.ResourceId, request.ParentResourceId) - if err != nil { - l.Error("error: deleteV2 resource failed", zap.Error(err)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: delete resource failed: %w", err) - } - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.DeleteResourceV2Response{Annotations: annos}, nil +func WithMetricsHandler(h metrics.Handler) Opt { + return func(b *builder) error { + b.m = metrics.New(h) + return nil } +} - rsDeleter, ok = b.resourceManagers[rt] - if !ok { - rsDeleter, ok = b.resourceDeleters[rt] +func WithSessionStore(ss sessions.SessionStore) Opt { + return func(b *builder) error { + b.sessionStore = ss + return nil } - if ok { - annos, err := rsDeleter.Delete(ctx, request.GetResourceId()) - if err != nil { - l.Error("error: delete resource failed", zap.Error(err)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: delete resource failed: %w", err) +} + +func (b *builder) options(opts ...Opt) error { + for _, opt := range opts { + if err := opt(b); err != nil { + return err } - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.DeleteResourceV2Response{Annotations: annos}, nil } - l.Error("error: resource type does not have resource Delete() configured", zap.String("resource_type", rt)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, status.Error(codes.Unimplemented, fmt.Sprintf("resource type %s does not have resource Delete() configured", rt)) -} -func (b *builderImpl) RotateCredential(ctx context.Context, request *v2.RotateCredentialRequest) (*v2.RotateCredentialResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.RotateCredential") - defer span.End() + return nil +} - start := b.nowFunc() - tt := tasks.RotateCredentialsType - l := ctxzap.Extract(ctx) - rt := request.GetResourceId().GetResourceType() - manager, ok := b.credentialManagers[rt] - if !ok { - l.Error("error: resource type does not have credential manager configured", zap.String("resource_type", rt)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, status.Error(codes.Unimplemented, "resource type does not have credential manager configured") +func (b *builder) addConnectorBuilderProviders(_ context.Context, in interface{}) error { + if mp, ok := in.(MetadataProvider); ok { + b.metadataProvider = mp + } else { + return fmt.Errorf("error: metadata provider not implemented") } - opts, err := crypto.ConvertCredentialOptions(ctx, b.clientSecret, request.GetCredentialOptions(), request.GetEncryptionConfigs()) - if err != nil { - l.Error("error: converting credential options failed", zap.Error(err)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: converting credential options failed: %w", err) + if vp, ok := in.(ValidateProvider); ok { + b.validateProvider = vp + } else { + return fmt.Errorf("error: validate provider not implemented") } - plaintexts, annos, err := manager.Rotate(ctx, request.GetResourceId(), opts) + return nil +} + +// GetMetadata gets all metadata for a connector. +func (b *builder) GetMetadata(ctx context.Context, request *v2.ConnectorServiceGetMetadataRequest) (*v2.ConnectorServiceGetMetadataResponse, error) { + ctx, span := tracer.Start(ctx, "builder.GetMetadata") + defer span.End() + + start := b.nowFunc() + tt := tasks.GetMetadataType + md, err := b.metadataProvider.Metadata(ctx) if err != nil { - l.Error("error: rotate credentials on resource failed", zap.Error(err)) b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: rotate credentials on resource failed: %w", err) + return nil, err } - pkem, err := crypto.NewEncryptionManager(request.GetCredentialOptions(), request.GetEncryptionConfigs()) + md.Capabilities, err = b.getCapabilities(ctx) if err != nil { - l.Error("error: creating encryption manager failed", zap.Error(err)) b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: creating encryption manager failed: %w", err) + return nil, err } - var encryptedDatas []*v2.EncryptedData - for _, plaintextCredential := range plaintexts { - encryptedData, err := pkem.Encrypt(ctx, plaintextCredential) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, err - } - encryptedDatas = append(encryptedDatas, encryptedData...) + annos := annotations.Annotations(md.Annotations) + if b.ticketManager != nil { + annos.Append(&v2.ExternalTicketSettings{Enabled: b.ticketingEnabled}) } + md.Annotations = annos b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.RotateCredentialResponse{ - Annotations: annos, - ResourceId: request.GetResourceId(), - EncryptedData: encryptedDatas, - }, nil + return &v2.ConnectorServiceGetMetadataResponse{Metadata: md}, nil } -func (b *builderImpl) Cleanup(ctx context.Context, request *v2.ConnectorServiceCleanupRequest) (*v2.ConnectorServiceCleanupResponse, error) { - l := ctxzap.Extract(ctx) +// Validate validates the connector. +func (b *builder) Validate(ctx context.Context, request *v2.ConnectorServiceValidateRequest) (*v2.ConnectorServiceValidateResponse, error) { + ctx, span := tracer.Start(ctx, "builder.Validate") + defer span.End() - // Clear session cache if available in context - sessionCache, err := session.GetSession(ctx) - if err != nil { - l.Warn("error getting session cache", zap.Error(err)) - } else if request.GetActiveSyncId() != "" { - err = sessionCache.Clear(ctx, session.WithSyncID(request.GetActiveSyncId())) - if err != nil { - l.Warn("error clearing session cache", zap.Error(err)) + retryer := retry.NewRetryer(ctx, retry.RetryConfig{ + MaxAttempts: 5, + InitialDelay: 1 * time.Second, + MaxDelay: 0, + }) + + for { + annos, err := b.validateProvider.Validate(ctx) + if err == nil { + return &v2.ConnectorServiceValidateResponse{ + Annotations: annos, + SdkVersion: sdk.Version, + }, nil + } + + if retryer.ShouldWaitAndRetry(ctx, err) { + continue } + + return nil, fmt.Errorf("validate failed: %w", err) } +} + +func (b *builder) Cleanup(ctx context.Context, request *v2.ConnectorServiceCleanupRequest) (*v2.ConnectorServiceCleanupResponse, error) { + l := ctxzap.Extract(ctx) + // TODO(kans): clear the session store here. // Clear all http caches at the end of a sync. This must be run in the child process, which is why it's in this function and not in syncer.go - err = uhttp.ClearCaches(ctx) + err := uhttp.ClearCaches(ctx) if err != nil { l.Warn("error clearing http caches", zap.Error(err)) } @@ -1461,175 +312,138 @@ func (b *builderImpl) Cleanup(ctx context.Context, request *v2.ConnectorServiceC return resp, err } -func (b *builderImpl) CreateAccount(ctx context.Context, request *v2.CreateAccountRequest) (*v2.CreateAccountResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.CreateAccount") - defer span.End() - - start := b.nowFunc() - tt := tasks.CreateAccountType - l := ctxzap.Extract(ctx) - if b.accountManager == nil { - l.Error("error: connector does not have account manager configured") - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, status.Error(codes.Unimplemented, "connector does not have credential manager configured") - } - - opts, err := crypto.ConvertCredentialOptions(ctx, b.clientSecret, request.GetCredentialOptions(), request.GetEncryptionConfigs()) - if err != nil { - l.Error("error: converting credential options failed", zap.Error(err)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: converting credential options failed: %w", err) - } +// getCapabilities gets all capabilities for a connector. +func (b *builder) getCapabilities(ctx context.Context) (*v2.ConnectorCapabilities, error) { + connectorCaps := make(map[v2.Capability]struct{}) + resourceTypeCapabilities := []*v2.ResourceTypeCapability{} - result, plaintexts, annos, err := b.accountManager.CreateAccount(ctx, request.GetAccountInfo(), opts) - if err != nil { - l.Error("error: create account failed", zap.Error(err)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: create account failed: %w", err) - } + for resourceTypeID, rb := range b.resourceSyncers { + connectorCaps[v2.Capability_CAPABILITY_SYNC] = struct{}{} + caps := []v2.Capability{v2.Capability_CAPABILITY_SYNC} - pkem, err := crypto.NewEncryptionManager(request.GetCredentialOptions(), request.GetEncryptionConfigs()) - if err != nil { - l.Error("error: creating encryption manager failed", zap.Error(err)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: creating encryption manager failed: %w", err) - } + if _, exists := b.resourceTargetedSyncers[resourceTypeID]; exists { + caps = append(caps, v2.Capability_CAPABILITY_TARGETED_SYNC) + } - var encryptedDatas []*v2.EncryptedData - for _, plaintextCredential := range plaintexts { - encryptedData, err := pkem.Encrypt(ctx, plaintextCredential) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, err + if _, exists := b.resourceProvisioners[resourceTypeID]; exists { + caps = append(caps, v2.Capability_CAPABILITY_PROVISION) } - encryptedDatas = append(encryptedDatas, encryptedData...) - } - rv := &v2.CreateAccountResponse{ - EncryptedData: encryptedDatas, - Annotations: annos, - } + if _, exists := b.accountManagers[resourceTypeID]; exists { + caps = append(caps, v2.Capability_CAPABILITY_ACCOUNT_PROVISIONING) + } - switch r := result.(type) { - case *v2.CreateAccountResponse_SuccessResult: - rv.Result = &v2.CreateAccountResponse_Success{Success: r} - case *v2.CreateAccountResponse_ActionRequiredResult: - rv.Result = &v2.CreateAccountResponse_ActionRequired{ActionRequired: r} - default: - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, status.Error(codes.Unimplemented, fmt.Sprintf("unknown result type: %T", result)) - } + if _, exists := b.resourceManagers[resourceTypeID]; exists { + caps = append(caps, v2.Capability_CAPABILITY_RESOURCE_DELETE, v2.Capability_CAPABILITY_RESOURCE_CREATE) + } else if _, exists := b.resourceDeleters[resourceTypeID]; exists { + caps = append(caps, v2.Capability_CAPABILITY_RESOURCE_DELETE) + } - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return rv, nil -} + if _, exists := b.credentialManagers[resourceTypeID]; exists { + caps = append(caps, v2.Capability_CAPABILITY_CREDENTIAL_ROTATION) + } -func (b *builderImpl) ListActionSchemas(ctx context.Context, request *v2.ListActionSchemasRequest) (*v2.ListActionSchemasResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.ListActionSchemas") - defer span.End() + // Extend the capabilities with the resource type specificcapabilities + for _, cap := range caps { + connectorCaps[cap] = struct{}{} + } - start := b.nowFunc() - tt := tasks.ActionListSchemasType - if b.actionManager == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: action manager not implemented") + resourceTypeCapabilities = append(resourceTypeCapabilities, &v2.ResourceTypeCapability{ + ResourceType: rb.ResourceType(ctx), + Capabilities: caps, + }) } - actionSchemas, annos, err := b.actionManager.ListActionSchemas(ctx) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: listing action schemas failed: %w", err) + // Check for account provisioning capability (global, not per resource type) + if b.accountManager != nil { + connectorCaps[v2.Capability_CAPABILITY_ACCOUNT_PROVISIONING] = struct{}{} } + sort.Slice(resourceTypeCapabilities, func(i, j int) bool { + return resourceTypeCapabilities[i].ResourceType.GetId() < resourceTypeCapabilities[j].ResourceType.GetId() + }) - rv := &v2.ListActionSchemasResponse{ - Schemas: actionSchemas, - Annotations: annos, + if len(b.eventFeeds) > 0 { + connectorCaps[v2.Capability_CAPABILITY_EVENT_FEED_V2] = struct{}{} } - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return rv, nil -} - -func (b *builderImpl) GetActionSchema(ctx context.Context, request *v2.GetActionSchemaRequest) (*v2.GetActionSchemaResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.GetActionSchema") - defer span.End() + if b.ticketManager != nil { + connectorCaps[v2.Capability_CAPABILITY_TICKETING] = struct{}{} + } - start := b.nowFunc() - tt := tasks.ActionGetSchemaType - if b.actionManager == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: action manager not implemented") + if b.actionManager != nil { + connectorCaps[v2.Capability_CAPABILITY_ACTIONS] = struct{}{} } - actionSchema, annos, err := b.actionManager.GetActionSchema(ctx, request.GetName()) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: getting action schema failed: %w", err) + var caps []v2.Capability + for c := range connectorCaps { + caps = append(caps, c) } + slices.Sort(caps) - rv := &v2.GetActionSchemaResponse{ - Schema: actionSchema, - Annotations: annos, + credDetails, err := getCredentialDetails(ctx, b) + if err != nil { + return nil, err } - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return rv, nil + return &v2.ConnectorCapabilities{ + ResourceTypeCapabilities: resourceTypeCapabilities, + ConnectorCapabilities: caps, + CredentialDetails: credDetails, + }, nil } -func (b *builderImpl) InvokeAction(ctx context.Context, request *v2.InvokeActionRequest) (*v2.InvokeActionResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.InvokeAction") - defer span.End() - - start := b.nowFunc() - tt := tasks.ActionInvokeType - if b.actionManager == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: action manager not implemented") - } - - id, status, resp, annos, err := b.actionManager.InvokeAction(ctx, request.GetName(), request.GetArgs()) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: invoking action failed: %w", err) +func validateCapabilityDetails(_ context.Context, credDetails *v2.CredentialDetails) error { + if credDetails.CapabilityAccountProvisioning != nil { + // Ensure that the preferred option is included and is part of the supported options + if credDetails.CapabilityAccountProvisioning.PreferredCredentialOption == v2.CapabilityDetailCredentialOption_CAPABILITY_DETAIL_CREDENTIAL_OPTION_UNSPECIFIED { + return status.Error(codes.InvalidArgument, "error: preferred credential creation option is not set") + } + if !slices.Contains(credDetails.CapabilityAccountProvisioning.SupportedCredentialOptions, credDetails.CapabilityAccountProvisioning.PreferredCredentialOption) { + return status.Error(codes.InvalidArgument, "error: preferred credential creation option is not part of the supported options") + } } - rv := &v2.InvokeActionResponse{ - Id: id, - Name: request.GetName(), - Status: status, - Annotations: annos, - Response: resp, + if credDetails.CapabilityCredentialRotation != nil { + // Ensure that the preferred option is included and is part of the supported options + if credDetails.CapabilityCredentialRotation.PreferredCredentialOption == v2.CapabilityDetailCredentialOption_CAPABILITY_DETAIL_CREDENTIAL_OPTION_UNSPECIFIED { + return status.Error(codes.InvalidArgument, "error: preferred credential rotation option is not set") + } + if !slices.Contains(credDetails.CapabilityCredentialRotation.SupportedCredentialOptions, credDetails.CapabilityCredentialRotation.PreferredCredentialOption) { + return status.Error(codes.InvalidArgument, "error: preferred credential rotation option is not part of the supported options") + } } - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return rv, nil + return nil } -func (b *builderImpl) GetActionStatus(ctx context.Context, request *v2.GetActionStatusRequest) (*v2.GetActionStatusResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.GetActionStatus") - defer span.End() +func getCredentialDetails(ctx context.Context, b *builder) (*v2.CredentialDetails, error) { + l := ctxzap.Extract(ctx) + rv := &v2.CredentialDetails{} - start := b.nowFunc() - tt := tasks.ActionStatusType - if b.actionManager == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: action manager not implemented") + // Check for account provisioning capability details + if b.accountManager != nil { + accountProvisioningCapabilityDetails, _, err := b.accountManager.CreateAccountCapabilityDetails(ctx) + if err != nil { + l.Error("error: getting account provisioning details", zap.Error(err)) + return nil, fmt.Errorf("error: getting account provisioning details: %w", err) + } + rv.CapabilityAccountProvisioning = accountProvisioningCapabilityDetails } - status, name, rv, annos, err := b.actionManager.GetActionStatus(ctx, request.GetId()) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: getting action status failed: %w", err) + // Check for credential rotation capability details + for _, cm := range b.credentialManagers { + credentialRotationCapabilityDetails, _, err := cm.RotateCapabilityDetails(ctx) + if err != nil { + l.Error("error: getting credential management details", zap.Error(err)) + return nil, fmt.Errorf("error: getting credential management details: %w", err) + } + rv.CapabilityCredentialRotation = credentialRotationCapabilityDetails + break // Only need one credential manager's details } - resp := &v2.GetActionStatusResponse{ - Id: request.GetId(), - Name: name, - Status: status, - Annotations: annos, - Response: rv, + err := validateCapabilityDetails(ctx, rv) + if err != nil { + return nil, fmt.Errorf("error: validating capability details: %w", err) } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return resp, nil + return rv, nil } diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/credentials.go b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/credentials.go new file mode 100644 index 00000000..1ae8fb0e --- /dev/null +++ b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/credentials.go @@ -0,0 +1,105 @@ +package connectorbuilder + +import ( + "context" + "fmt" + + v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + "github.com/conductorone/baton-sdk/pkg/annotations" + "github.com/conductorone/baton-sdk/pkg/crypto" + "github.com/conductorone/baton-sdk/pkg/types/tasks" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// CredentialManager extends ResourceSyncer to add capabilities for managing credentials. +// Implementing this interface indicates the connector supports rotating credentials +// for resources of the associated type. This is commonly used for user accounts +// or service accounts that have rotatable credentials. +type CredentialManager interface { + ResourceSyncer + CredentialManagerLimited +} + +type CredentialManagerLimited interface { + Rotate(ctx context.Context, + resourceId *v2.ResourceId, + credentialOptions *v2.LocalCredentialOptions) ([]*v2.PlaintextData, annotations.Annotations, error) + RotateCapabilityDetails(ctx context.Context) (*v2.CredentialDetailsCredentialRotation, annotations.Annotations, error) +} + +type OldCredentialManager interface { + Rotate(ctx context.Context, + resourceId *v2.ResourceId, + credentialOptions *v2.CredentialOptions) ([]*v2.PlaintextData, annotations.Annotations, error) +} + +func (b *builder) RotateCredential(ctx context.Context, request *v2.RotateCredentialRequest) (*v2.RotateCredentialResponse, error) { + ctx, span := tracer.Start(ctx, "builder.RotateCredential") + defer span.End() + + start := b.nowFunc() + tt := tasks.RotateCredentialsType + l := ctxzap.Extract(ctx) + rt := request.GetResourceId().GetResourceType() + manager, ok := b.credentialManagers[rt] + if !ok { + l.Error("error: resource type does not have credential manager configured", zap.String("resource_type", rt)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, status.Error(codes.Unimplemented, "resource type does not have credential manager configured") + } + + opts, err := crypto.ConvertCredentialOptions(ctx, b.clientSecret, request.GetCredentialOptions(), request.GetEncryptionConfigs()) + if err != nil { + l.Error("error: converting credential options failed", zap.Error(err)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: converting credential options failed: %w", err) + } + + plaintexts, annos, err := manager.Rotate(ctx, request.GetResourceId(), opts) + if err != nil { + l.Error("error: rotate credentials on resource failed", zap.Error(err)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: rotate credentials on resource failed: %w", err) + } + + pkem, err := crypto.NewEncryptionManager(request.GetCredentialOptions(), request.GetEncryptionConfigs()) + if err != nil { + l.Error("error: creating encryption manager failed", zap.Error(err)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: creating encryption manager failed: %w", err) + } + + var encryptedDatas []*v2.EncryptedData + for _, plaintextCredential := range plaintexts { + encryptedData, err := pkem.Encrypt(ctx, plaintextCredential) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, err + } + encryptedDatas = append(encryptedDatas, encryptedData...) + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.RotateCredentialResponse{ + Annotations: annos, + ResourceId: request.GetResourceId(), + EncryptedData: encryptedDatas, + }, nil +} + +func (b *builder) addCredentialManager(_ context.Context, typeId string, in interface{}) error { + if _, ok := in.(OldCredentialManager); ok { + return fmt.Errorf("error: old credential manager interface implemented for %s", typeId) + } + + if credentialManagers, ok := in.(CredentialManagerLimited); ok { + if _, ok := b.credentialManagers[typeId]; ok { + return fmt.Errorf("error: duplicate resource type found for credential manager %s", typeId) + } + b.credentialManagers[typeId] = credentialManagers + } + return nil +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/events.go b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/events.go new file mode 100644 index 00000000..c7f0f6b6 --- /dev/null +++ b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/events.go @@ -0,0 +1,163 @@ +package connectorbuilder + +import ( + "context" + "fmt" + + v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + "github.com/conductorone/baton-sdk/pkg/annotations" + "github.com/conductorone/baton-sdk/pkg/pagination" + "github.com/conductorone/baton-sdk/pkg/types/tasks" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" +) + +const ( + LegacyBatonFeedId = "baton_feed_event" +) + +// Deprecated: This interface is deprecated in favor of EventProviderV2 which supports +// multiple event feeds. Implementing this interface indicates the connector can provide +// a single stream of events from the external system, enabling near real-time updates +// in Baton. New connectors should implement EventProviderV2 instead. +type EventProvider interface { + ConnectorBuilder + EventLister +} + +// NewEventProviderV2 is a new interface that allows connectors to provide multiple event feeds. +// +// This is the recommended interface for implementing event feed support in new connectors. +type EventProviderV2 interface { + ConnectorBuilder + EventFeedsLimited +} + +type EventFeedsLimited interface { + EventFeeds(ctx context.Context) []EventFeed +} + +// EventFeed is a single stream of events from the external system. +// +// EventFeedMetadata describes this feed, and a connector can have multiple feeds. +type EventFeed interface { + EventLister + EventFeedLimited +} + +type EventFeedLimited interface { + EventFeedMetadata(ctx context.Context) *v2.EventFeedMetadata +} + +// Compatibility interface lets us handle both EventFeed and EventProvider the same. +type EventLister interface { + ListEvents(ctx context.Context, earliestEvent *timestamppb.Timestamp, pToken *pagination.StreamToken) ([]*v2.Event, *pagination.StreamState, annotations.Annotations, error) +} + +func newEventFeedV1to2(eventFeed EventLister) EventFeed { + return &oldEventFeedWrapper{ + feed: eventFeed, + } +} + +type oldEventFeedWrapper struct { + feed EventLister +} + +func (e *oldEventFeedWrapper) EventFeedMetadata(ctx context.Context) *v2.EventFeedMetadata { + return &v2.EventFeedMetadata{ + Id: LegacyBatonFeedId, + SupportedEventTypes: []v2.EventType{v2.EventType_EVENT_TYPE_UNSPECIFIED}, + } +} + +func (e *oldEventFeedWrapper) ListEvents( + ctx context.Context, + earliestEvent *timestamppb.Timestamp, + pToken *pagination.StreamToken, +) ([]*v2.Event, *pagination.StreamState, annotations.Annotations, error) { + return e.feed.ListEvents(ctx, earliestEvent, pToken) +} + +func (b *builder) ListEventFeeds(ctx context.Context, request *v2.ListEventFeedsRequest) (*v2.ListEventFeedsResponse, error) { + ctx, span := tracer.Start(ctx, "builder.ListEventFeeds") + defer span.End() + + start := b.nowFunc() + tt := tasks.ListEventFeedsType + + feeds := make([]*v2.EventFeedMetadata, 0, len(b.eventFeeds)) + + for _, feed := range b.eventFeeds { + feeds = append(feeds, feed.EventFeedMetadata(ctx)) + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.ListEventFeedsResponse{ + List: feeds, + }, nil +} + +func (b *builder) ListEvents(ctx context.Context, request *v2.ListEventsRequest) (*v2.ListEventsResponse, error) { + ctx, span := tracer.Start(ctx, "builder.ListEvents") + defer span.End() + + start := b.nowFunc() + feedId := request.GetEventFeedId() + + // If no feedId is provided, use the legacy Baton feed Id + if feedId == "" { + feedId = LegacyBatonFeedId + } + + feed, ok := b.eventFeeds[feedId] + if !ok { + return nil, status.Errorf(codes.NotFound, "error: event feed not found") + } + + tt := tasks.ListEventsType + events, streamState, annotations, err := feed.ListEvents(ctx, request.StartAt, &pagination.StreamToken{ + Size: int(request.PageSize), + Cursor: request.Cursor, + }) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: listing events failed: %w", err) + } + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.ListEventsResponse{ + Events: events, + Cursor: streamState.Cursor, + HasMore: streamState.HasMore, + Annotations: annotations, + }, nil +} + +func (b *builder) addEventFeed(ctx context.Context, in interface{}) error { + if ep, ok := in.(EventFeedsLimited); ok { + for _, ef := range ep.EventFeeds(ctx) { + feedData := ef.EventFeedMetadata(ctx) + if feedData == nil { + return fmt.Errorf("error: event feed metadata is nil") + } + if err := feedData.Validate(); err != nil { + return fmt.Errorf("error: event feed metadata for %s is invalid: %w", feedData.Id, err) + } + if _, ok := b.eventFeeds[feedData.Id]; ok { + return fmt.Errorf("error: duplicate event feed id found: %s", feedData.Id) + } + b.eventFeeds[feedData.Id] = ef + } + } + + if ep, ok := in.(EventLister); ok { + // Register the legacy Baton feed as a v2 event feed + // implementing both v1 and v2 event feeds is not supported. + if len(b.eventFeeds) != 0 { + return fmt.Errorf("error: using legacy event feed is not supported when using EventProviderV2") + } + b.eventFeeds[LegacyBatonFeedId] = newEventFeedV1to2(ep) + } + return nil +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_manager.go b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_manager.go new file mode 100644 index 00000000..a393e3c9 --- /dev/null +++ b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_manager.go @@ -0,0 +1,230 @@ +package connectorbuilder + +import ( + "context" + "fmt" + + v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + "github.com/conductorone/baton-sdk/pkg/annotations" + "github.com/conductorone/baton-sdk/pkg/types/tasks" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// ResourceManager extends ResourceSyncer to add capabilities for creating resources. +// +// Implementing this interface indicates the connector supports creating and deleting resources +// of the associated resource type. A ResourceManager automatically provides ResourceDeleter +// functionality. +type ResourceManager interface { + ResourceSyncer + ResourceManagerLimited +} + +type ResourceManagerLimited interface { + ResourceCreator + ResourceDeleterLimited +} + +type ResourceManagerV2Limited interface { + ResourceCreator + ResourceDeleterV2Limited +} + +// ResourceManagerV2 extends ResourceSyncer to add capabilities for creating resources. +// +// This is the recommended interface for implementing resource creation operations in new connectors. +type ResourceManagerV2 interface { + ResourceSyncer + ResourceManagerV2Limited +} + +type ResourceCreator interface { + Create(ctx context.Context, resource *v2.Resource) (*v2.Resource, annotations.Annotations, error) +} + +// ResourceDeleter extends ResourceSyncer to add capabilities for deleting resources. +// +// Implementing this interface indicates the connector supports deleting resources +// of the associated resource type. +type ResourceDeleter interface { + ResourceSyncer + ResourceDeleterLimited +} +type ResourceDeleterLimited interface { + Delete(ctx context.Context, resourceId *v2.ResourceId) (annotations.Annotations, error) +} + +// ResourceDeleterV2 extends ResourceSyncer to add capabilities for deleting resources. +// +// This is the recommended interface for implementing resource deletion operations in new connectors. +// It differs from ResourceDeleter by having the resource, not just the id. +type ResourceDeleterV2 interface { + ResourceSyncer + ResourceDeleterV2Limited +} + +type ResourceDeleterV2Limited interface { + Delete(ctx context.Context, resourceId *v2.ResourceId, parentResourceID *v2.ResourceId) (annotations.Annotations, error) +} + +func (b *builder) CreateResource(ctx context.Context, request *v2.CreateResourceRequest) (*v2.CreateResourceResponse, error) { + ctx, span := tracer.Start(ctx, "builder.CreateResource") + defer span.End() + + start := b.nowFunc() + tt := tasks.CreateResourceType + l := ctxzap.Extract(ctx) + rt := request.GetResource().GetId().GetResourceType() + + manager, ok := b.resourceManagers[rt] + if !ok { + l.Error("error: resource type does not have resource Create() configured", zap.String("resource_type", rt)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, status.Error(codes.Unimplemented, fmt.Sprintf("resource type %s does not have resource Create() configured", rt)) + } + resource, annos, err := manager.Create(ctx, request.Resource) + if err != nil { + l.Error("error: create resource failed", zap.Error(err)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: create resource failed: %w", err) + } + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.CreateResourceResponse{Created: resource, Annotations: annos}, nil +} + +func (b *builder) DeleteResource(ctx context.Context, request *v2.DeleteResourceRequest) (*v2.DeleteResourceResponse, error) { + ctx, span := tracer.Start(ctx, "builder.DeleteResource") + defer span.End() + + start := b.nowFunc() + tt := tasks.DeleteResourceType + + l := ctxzap.Extract(ctx) + rt := request.GetResourceId().GetResourceType() + var rsDeleter ResourceDeleterV2Limited + var ok bool + + rsDeleter, ok = b.resourceManagers[rt] + if !ok { + rsDeleter, ok = b.resourceDeleters[rt] + } + + if !ok { + l.Error("error: resource type does not have resource Delete() configured", zap.String("resource_type", rt)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, status.Error(codes.Unimplemented, fmt.Sprintf("resource type %s does not have resource Delete() configured", rt)) + } + + annos, err := rsDeleter.Delete(ctx, request.ResourceId, request.ParentResourceId) + if err != nil { + l.Error("error: deleteV2 resource failed", zap.Error(err)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: delete resource failed: %w", err) + } + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.DeleteResourceResponse{Annotations: annos}, nil +} + +func (b *builder) DeleteResourceV2(ctx context.Context, request *v2.DeleteResourceV2Request) (*v2.DeleteResourceV2Response, error) { + ctx, span := tracer.Start(ctx, "builder.DeleteResourceV2") + defer span.End() + + start := b.nowFunc() + tt := tasks.DeleteResourceType + + l := ctxzap.Extract(ctx) + rt := request.GetResourceId().GetResourceType() + var rsDeleter ResourceDeleterV2Limited + var ok bool + + rsDeleter, ok = b.resourceManagers[rt] + if !ok { + rsDeleter, ok = b.resourceDeleters[rt] + } + + if !ok { + l.Error("error: resource type does not have resource Delete() configured", zap.String("resource_type", rt)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, status.Error(codes.Unimplemented, fmt.Sprintf("resource type %s does not have resource Delete() configured", rt)) + } + + annos, err := rsDeleter.Delete(ctx, request.ResourceId, request.ParentResourceId) + if err != nil { + l.Error("error: deleteV2 resource failed", zap.Error(err)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: delete resource failed: %w", err) + } + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.DeleteResourceV2Response{Annotations: annos}, nil +} + +func newResourceManager1to2(resourceManager ResourceManagerLimited) ResourceManagerV2Limited { + return &resourceManager1to2{resourceManager} +} + +type resourceManager1to2 struct { + ResourceManagerLimited +} + +func (r *resourceManager1to2) Delete(ctx context.Context, resourceId *v2.ResourceId, parentResourceID *v2.ResourceId) (annotations.Annotations, error) { + return r.ResourceManagerLimited.Delete(ctx, resourceId) +} + +func newDeleter1to2(resourceDeleter ResourceDeleterLimited) ResourceDeleterV2Limited { + return &deleter1to2{resourceDeleter} +} + +type deleter1to2 struct { + ResourceDeleterLimited +} + +func (d *deleter1to2) Delete(ctx context.Context, resourceId *v2.ResourceId, parentResourceID *v2.ResourceId) (annotations.Annotations, error) { + // Just drop the parentResourceID... + return d.ResourceDeleterLimited.Delete(ctx, resourceId) +} + +func (b *builder) addResourceManager(_ context.Context, typeId string, in interface{}) error { + if resourceManager, ok := in.(ResourceManagerLimited); ok { + if _, ok := b.resourceManagers[typeId]; ok { + return fmt.Errorf("error: duplicate resource type found for resource manager %s", typeId) + } + b.resourceManagers[typeId] = newResourceManager1to2(resourceManager) + // Support DeleteResourceV2 if connector implements both Create and Delete + if _, ok := b.resourceDeleters[typeId]; ok { + // This should never happen + return fmt.Errorf("error: duplicate resource type found for resource deleter %s", typeId) + } + b.resourceDeleters[typeId] = newDeleter1to2(resourceManager) + } else { + if resourceDeleter, ok := in.(ResourceDeleterLimited); ok { + if _, ok := b.resourceDeleters[typeId]; ok { + return fmt.Errorf("error: duplicate resource type found for resource deleter %s", typeId) + } + b.resourceDeleters[typeId] = newDeleter1to2(resourceDeleter) + } + } + + if resourceManager, ok := in.(ResourceManagerV2Limited); ok { + if _, ok := b.resourceManagers[typeId]; ok { + return fmt.Errorf("error: duplicate resource type found for resource managerV2 %s", typeId) + } + b.resourceManagers[typeId] = resourceManager + // Support DeleteResourceV2 if connector implements both Create and Delete + if _, ok := b.resourceDeleters[typeId]; ok { + // This should never happen + return fmt.Errorf("error: duplicate resource type found for resource deleterV2 %s", typeId) + } + b.resourceDeleters[typeId] = resourceManager + } else { + if resourceDeleter, ok := in.(ResourceDeleterV2Limited); ok { + if _, ok := b.resourceDeleters[typeId]; ok { + return fmt.Errorf("error: duplicate resource type found for resource deleterV2 %s", typeId) + } + b.resourceDeleters[typeId] = resourceDeleter + } + } + return nil +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_provisioner.go b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_provisioner.go new file mode 100644 index 00000000..ef7cbf70 --- /dev/null +++ b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_provisioner.go @@ -0,0 +1,173 @@ +package connectorbuilder + +import ( + "context" + "fmt" + "time" + + v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + "github.com/conductorone/baton-sdk/pkg/annotations" + "github.com/conductorone/baton-sdk/pkg/retry" + "github.com/conductorone/baton-sdk/pkg/types/tasks" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" + "go.uber.org/zap" +) + +// ResourceProvisioner extends ResourceSyncer to add capabilities for granting and revoking access. +// +// Note: ResourceProvisionerV2 is preferred for new connectors as it provides +// enhanced grant capabilities. +// +// Implementing this interface indicates the connector supports provisioning operations +// for the associated resource type. +type ResourceProvisioner interface { + ResourceSyncer + ResourceProvisionerLimited +} + +type ResourceProvisionerLimited interface { + RevokeProvisioner + GrantProvisioner +} + +type RevokeProvisioner interface { + Revoke(ctx context.Context, grant *v2.Grant) (annotations.Annotations, error) +} + +type GrantProvisioner interface { + Grant(ctx context.Context, resource *v2.Resource, entitlement *v2.Entitlement) (annotations.Annotations, error) +} + +// ResourceProvisionerV2 extends ResourceSyncer to add capabilities for granting and revoking access +// with enhanced functionality compared to ResourceProvisioner. +// +// This is the recommended interface for implementing provisioning operations in new connectors. +// It differs from ResourceProvisioner by returning a list of grants from the Grant method. +type ResourceProvisionerV2 interface { + ResourceSyncer + ResourceProvisionerV2Limited +} + +type ResourceProvisionerV2Limited interface { + RevokeProvisioner + GrantProvisionerV2 +} + +type GrantProvisionerV2 interface { + Grant(ctx context.Context, resource *v2.Resource, entitlement *v2.Entitlement) ([]*v2.Grant, annotations.Annotations, error) +} + +func (b *builder) Grant(ctx context.Context, request *v2.GrantManagerServiceGrantRequest) (*v2.GrantManagerServiceGrantResponse, error) { + ctx, span := tracer.Start(ctx, "builder.Grant") + defer span.End() + + start := b.nowFunc() + tt := tasks.GrantType + l := ctxzap.Extract(ctx) + + rt := request.Entitlement.Resource.Id.ResourceType + + provisioner, ok := b.resourceProvisioners[rt] + + if !ok { + l.Error("error: resource type does not have provisioner configured", zap.String("resource_type", rt)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: resource type does not have provisioner configured") + } + + retryer := retry.NewRetryer(ctx, retry.RetryConfig{ + MaxAttempts: 3, + InitialDelay: 15 * time.Second, + MaxDelay: 60 * time.Second, + }) + + for { + grants, annos, err := provisioner.Grant(ctx, request.Principal, request.Entitlement) + if err == nil { + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.GrantManagerServiceGrantResponse{Annotations: annos, Grants: grants}, nil + } + if retryer.ShouldWaitAndRetry(ctx, err) { + continue + } + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("grant failed: %w", err) + } +} + +func (b *builder) Revoke(ctx context.Context, request *v2.GrantManagerServiceRevokeRequest) (*v2.GrantManagerServiceRevokeResponse, error) { + ctx, span := tracer.Start(ctx, "builder.Revoke") + defer span.End() + + start := b.nowFunc() + tt := tasks.RevokeType + + l := ctxzap.Extract(ctx) + + rt := request.Grant.Entitlement.Resource.Id.ResourceType + + var revokeProvisioner RevokeProvisioner + provisioner, ok := b.resourceProvisioners[rt] + if ok { + revokeProvisioner = provisioner + } + + if revokeProvisioner == nil { + l.Error("error: resource type does not have provisioner configured", zap.String("resource_type", rt)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: resource type does not have provisioner configured") + } + + retryer := retry.NewRetryer(ctx, retry.RetryConfig{ + MaxAttempts: 3, + InitialDelay: 15 * time.Second, + MaxDelay: 60 * time.Second, + }) + + for { + annos, err := revokeProvisioner.Revoke(ctx, request.Grant) + if err == nil { + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.GrantManagerServiceRevokeResponse{Annotations: annos}, nil + } + if retryer.ShouldWaitAndRetry(ctx, err) { + continue + } + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("revoke failed: %w", err) + } +} + +func newResourceProvisionerV1to2(p ResourceProvisionerLimited) ResourceProvisionerV2Limited { + return &resourceProvisionerV1to2{ + ResourceProvisionerLimited: p, + } +} + +type resourceProvisionerV1to2 struct { + ResourceProvisionerLimited +} + +func (r *resourceProvisionerV1to2) Grant(ctx context.Context, resource *v2.Resource, entitlement *v2.Entitlement) ([]*v2.Grant, annotations.Annotations, error) { + annos, err := r.ResourceProvisionerLimited.Grant(ctx, resource, entitlement) + if err != nil { + return nil, annos, err + } + return nil, annos, nil +} + +func (b *builder) addProvisioner(_ context.Context, typeId string, in interface{}) error { + if provisioner, ok := in.(ResourceProvisionerLimited); ok { + if _, ok := b.resourceProvisioners[typeId]; ok { + return fmt.Errorf("error: duplicate resource type found for resource provisioner %s", typeId) + } + b.resourceProvisioners[typeId] = newResourceProvisionerV1to2(provisioner) + } + if provisioner, ok := in.(ResourceProvisionerV2Limited); ok { + if _, ok := b.resourceProvisioners[typeId]; ok { + return fmt.Errorf("error: duplicate resource type found for resource provisioner v2 %s", typeId) + } + b.resourceProvisioners[typeId] = provisioner + } + return nil +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_syncer.go b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_syncer.go new file mode 100644 index 00000000..a5e4641f --- /dev/null +++ b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_syncer.go @@ -0,0 +1,294 @@ +package connectorbuilder + +import ( + "context" + "fmt" + + v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + "github.com/conductorone/baton-sdk/pkg/annotations" + "github.com/conductorone/baton-sdk/pkg/pagination" + "github.com/conductorone/baton-sdk/pkg/types/resource" + "github.com/conductorone/baton-sdk/pkg/types/tasks" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// ResourceSyncer is the primary interface for connector developers to implement. +// +// It defines the core functionality for synchronizing resources, entitlements, and grants +// from external systems into Baton. Every connector must implement at least this interface +// for each resource type it supports. +// +// Extensions to this interface include: +// - ResourceProvisioner/ResourceProvisionerV2: For adding/removing access +// - ResourceManager: For creating and managing resources +// - ResourceDeleter: For deleting resources +// - AccountManager: For account provisioning operations +// - CredentialManager: For credential rotation operations. +// - ResourceTargetedSyncer: For directly getting a resource supporting targeted sync. + +type ResourceType interface { + ResourceType(ctx context.Context) *v2.ResourceType +} + +type ResourceSyncer interface { + ResourceType + List(ctx context.Context, parentResourceID *v2.ResourceId, pToken *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) + Entitlements(ctx context.Context, resource *v2.Resource, pToken *pagination.Token) ([]*v2.Entitlement, string, annotations.Annotations, error) + Grants(ctx context.Context, resource *v2.Resource, pToken *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) +} + +type ResourceSyncerLimited interface { + List(ctx context.Context, parentResourceID *v2.ResourceId, pToken *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) + Entitlements(ctx context.Context, resource *v2.Resource, pToken *pagination.Token) ([]*v2.Entitlement, string, annotations.Annotations, error) + Grants(ctx context.Context, resource *v2.Resource, pToken *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) +} + +type ResourceSyncerV2 interface { + ResourceType + ResourceSyncerV2Limited +} + +type ResourceSyncerV2Limited interface { + List(ctx context.Context, parentResourceID *v2.ResourceId, pToken *pagination.Token, opts resource.Options) ([]*v2.Resource, string, annotations.Annotations, error) + Entitlements(ctx context.Context, resource *v2.Resource, pToken *pagination.Token, opts resource.Options) ([]*v2.Entitlement, string, annotations.Annotations, error) + Grants(ctx context.Context, resource *v2.Resource, pToken *pagination.Token, opts resource.Options) ([]*v2.Grant, string, annotations.Annotations, error) +} + +// ResourceTargetedSyncer extends ResourceSyncer to add capabilities for directly syncing an individual resource +// +// Implementing this interface indicates the connector supports calling "get" on a resource +// of the associated resource type. +type ResourceTargetedSyncer interface { + ResourceSyncer + ResourceTargetedSyncerLimited +} + +type ResourceTargetedSyncerLimited interface { + Get(ctx context.Context, resourceId *v2.ResourceId, parentResourceId *v2.ResourceId) (*v2.Resource, annotations.Annotations, error) +} + +// ListResourceTypes lists all available resource types. +func (b *builder) ListResourceTypes( + ctx context.Context, + request *v2.ResourceTypesServiceListResourceTypesRequest, +) (*v2.ResourceTypesServiceListResourceTypesResponse, error) { + ctx, span := tracer.Start(ctx, "builder.ListResourceTypes") + defer span.End() + + start := b.nowFunc() + tt := tasks.ListResourceTypesType + var out []*v2.ResourceType + + if len(b.resourceSyncers) == 0 { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: no resource builders found") + } + + for _, rb := range b.resourceSyncers { + out = append(out, rb.ResourceType(ctx)) + } + + if len(out) == 0 { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: no resource types found") + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.ResourceTypesServiceListResourceTypesResponse{List: out}, nil +} + +// ListResources returns all available resources for a given resource type ID. +func (b *builder) ListResources(ctx context.Context, request *v2.ResourcesServiceListResourcesRequest) (*v2.ResourcesServiceListResourcesResponse, error) { + ctx, span := tracer.Start(ctx, "builder.ListResources") + defer span.End() + + start := b.nowFunc() + tt := tasks.ListResourcesType + rb, ok := b.resourceSyncers[request.ResourceTypeId] + if !ok { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: list resources with unknown resource type %s", request.ResourceTypeId) + } + out, nextPageToken, annos, err := rb.List(ctx, request.ParentResourceId, &pagination.Token{ + Size: int(request.PageSize), + Token: request.PageToken, + }, resource.Options{}) + + resp := &v2.ResourcesServiceListResourcesResponse{ + List: out, + NextPageToken: nextPageToken, + Annotations: annos, + } + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return resp, fmt.Errorf("error: listing resources failed: %w", err) + } + if request.PageToken != "" && request.PageToken == nextPageToken { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return resp, fmt.Errorf("error: listing resources failed: next page token is the same as the current page token. this is most likely a connector bug") + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return resp, nil +} + +func (b *builder) GetResource(ctx context.Context, request *v2.ResourceGetterServiceGetResourceRequest) (*v2.ResourceGetterServiceGetResourceResponse, error) { + ctx, span := tracer.Start(ctx, "builder.GetResource") + defer span.End() + + start := b.nowFunc() + tt := tasks.GetResourceType + resourceType := request.GetResourceId().GetResourceType() + rb, ok := b.resourceTargetedSyncers[resourceType] + if !ok { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, status.Errorf(codes.Unimplemented, "error: get resource with unknown resource type %s", resourceType) + } + resource, annos, err := rb.Get(ctx, request.GetResourceId(), request.GetParentResourceId()) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: get resource failed: %w", err) + } + if resource == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, status.Error(codes.NotFound, "error: get resource returned nil") + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.ResourceGetterServiceGetResourceResponse{ + Resource: resource, + Annotations: annos, + }, nil +} + +// ListEntitlements returns all the entitlements for a given resource. +func (b *builder) ListEntitlements(ctx context.Context, request *v2.EntitlementsServiceListEntitlementsRequest) (*v2.EntitlementsServiceListEntitlementsResponse, error) { + ctx, span := tracer.Start(ctx, "builder.ListEntitlements") + defer span.End() + + start := b.nowFunc() + tt := tasks.ListEntitlementsType + rb, ok := b.resourceSyncers[request.Resource.Id.ResourceType] + if !ok { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: list entitlements with unknown resource type %s", request.Resource.Id.ResourceType) + } + + out, nextPageToken, annos, err := rb.Entitlements(ctx, request.Resource, &pagination.Token{ + Size: int(request.PageSize), + Token: request.PageToken, + }, resource.Options{}) + + resp := &v2.EntitlementsServiceListEntitlementsResponse{ + List: out, + NextPageToken: nextPageToken, + Annotations: annos, + } + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return resp, fmt.Errorf("error: listing entitlements failed: %w", err) + } + if request.PageToken != "" && request.PageToken == nextPageToken { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return resp, fmt.Errorf("error: listing entitlements failed: next page token is the same as the current page token. this is most likely a connector bug") + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return resp, nil +} + +// ListGrants lists all the grants for a given resource. +func (b *builder) ListGrants(ctx context.Context, request *v2.GrantsServiceListGrantsRequest) (*v2.GrantsServiceListGrantsResponse, error) { + ctx, span := tracer.Start(ctx, "builder.ListGrants") + defer span.End() + + start := b.nowFunc() + tt := tasks.ListGrantsType + rid := request.Resource.Id + rb, ok := b.resourceSyncers[rid.ResourceType] + if !ok { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: list grants with unknown resource type %s", rid.ResourceType) + } + + out, nextPageToken, annos, err := rb.Grants(ctx, request.Resource, &pagination.Token{ + Size: int(request.PageSize), + Token: request.PageToken, + }, resource.Options{}) + + resp := &v2.GrantsServiceListGrantsResponse{ + List: out, + NextPageToken: nextPageToken, + Annotations: annos, + } + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return resp, fmt.Errorf("error: listing grants for resource %s/%s failed: %w", rid.ResourceType, rid.Resource, err) + } + if request.PageToken != "" && request.PageToken == nextPageToken { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return resp, fmt.Errorf("error: listing grants for resource %s/%s failed: next page token is the same as the current page token. this is most likely a connector bug", + rid.ResourceType, + rid.Resource) + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return resp, nil +} + +func newResourceSyncerV1toV2(rb ResourceSyncer) ResourceSyncerV2 { + return &resourceSyncerV1toV2{rb: rb} +} + +type resourceSyncerV1toV2 struct { + rb ResourceSyncer +} + +func (rw *resourceSyncerV1toV2) ResourceType(ctx context.Context) *v2.ResourceType { + return rw.rb.ResourceType(ctx) +} + +func (rw *resourceSyncerV1toV2) List(ctx context.Context, parentResourceID *v2.ResourceId, pToken *pagination.Token, opts resource.Options) ([]*v2.Resource, string, annotations.Annotations, error) { + return rw.rb.List(ctx, parentResourceID, pToken) +} + +func (rw *resourceSyncerV1toV2) Entitlements(ctx context.Context, resource *v2.Resource, pToken *pagination.Token, opts resource.Options) ([]*v2.Entitlement, string, annotations.Annotations, error) { + return rw.rb.Entitlements(ctx, resource, pToken) +} + +func (rw *resourceSyncerV1toV2) Grants(ctx context.Context, resource *v2.Resource, pToken *pagination.Token, opts resource.Options) ([]*v2.Grant, string, annotations.Annotations, error) { + return rw.rb.Grants(ctx, resource, pToken) +} + +func (b *builder) addTargetedSyncer(_ context.Context, typeId string, in interface{}) error { + if targetedSyncer, ok := in.(ResourceTargetedSyncerLimited); ok { + if _, ok := b.resourceTargetedSyncers[typeId]; ok { + return fmt.Errorf("error: duplicate resource type found for resource targeted syncer %s", typeId) + } + b.resourceTargetedSyncers[typeId] = targetedSyncer + } + return nil +} + +func (b *builder) addResourceSyncers(_ context.Context, typeId string, in interface{}) error { + // no duplicates + if _, ok := b.resourceSyncers[typeId]; ok { + return fmt.Errorf("error: duplicate resource type found for resource builder %s", typeId) + } + + if rb, ok := in.(ResourceSyncer); ok { + b.resourceSyncers[typeId] = newResourceSyncerV1toV2(rb) + } + + if rb, ok := in.(ResourceSyncerV2); ok { + b.resourceSyncers[typeId] = rb + } + + // A resource syncer is required + if _, ok := b.resourceSyncers[typeId]; !ok { + return fmt.Errorf("error: the resource syncer interface must be implemented for all types (%s)", typeId) + } + + return nil +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/tickets.go b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/tickets.go new file mode 100644 index 00000000..2339fb5f --- /dev/null +++ b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/tickets.go @@ -0,0 +1,242 @@ +package connectorbuilder + +import ( + "context" + "fmt" + "time" + + v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + "github.com/conductorone/baton-sdk/pkg/annotations" + "github.com/conductorone/baton-sdk/pkg/pagination" + "github.com/conductorone/baton-sdk/pkg/retry" + "github.com/conductorone/baton-sdk/pkg/types/tasks" +) + +// TicketManager extends ConnectorBuilder to add capabilities for ticket management. +// +// Implementing this interface indicates the connector can integrate with an external +// ticketing system, allowing Baton to create and track tickets in that system. +type TicketManager interface { + ConnectorBuilder + TicketManagerLimited +} + +type TicketManagerLimited interface { + GetTicket(ctx context.Context, ticketId string) (*v2.Ticket, annotations.Annotations, error) + CreateTicket(ctx context.Context, ticket *v2.Ticket, schema *v2.TicketSchema) (*v2.Ticket, annotations.Annotations, error) + GetTicketSchema(ctx context.Context, schemaID string) (*v2.TicketSchema, annotations.Annotations, error) + ListTicketSchemas(ctx context.Context, pToken *pagination.Token) ([]*v2.TicketSchema, string, annotations.Annotations, error) + BulkCreateTickets(context.Context, *v2.TicketsServiceBulkCreateTicketsRequest) (*v2.TicketsServiceBulkCreateTicketsResponse, error) + BulkGetTickets(context.Context, *v2.TicketsServiceBulkGetTicketsRequest) (*v2.TicketsServiceBulkGetTicketsResponse, error) +} + +func (b *builder) BulkCreateTickets(ctx context.Context, request *v2.TicketsServiceBulkCreateTicketsRequest) (*v2.TicketsServiceBulkCreateTicketsResponse, error) { + ctx, span := tracer.Start(ctx, "builder.BulkCreateTickets") + defer span.End() + + start := b.nowFunc() + tt := tasks.BulkCreateTicketsType + if b.ticketManager == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: ticket manager not implemented") + } + + reqBody := request.GetTicketRequests() + if len(reqBody) == 0 { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: request body had no items") + } + + ticketsResponse, err := b.ticketManager.BulkCreateTickets(ctx, request) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: creating tickets failed: %w", err) + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.TicketsServiceBulkCreateTicketsResponse{ + Tickets: ticketsResponse.GetTickets(), + }, nil +} + +func (b *builder) BulkGetTickets(ctx context.Context, request *v2.TicketsServiceBulkGetTicketsRequest) (*v2.TicketsServiceBulkGetTicketsResponse, error) { + ctx, span := tracer.Start(ctx, "builder.BulkGetTickets") + defer span.End() + + start := b.nowFunc() + tt := tasks.BulkGetTicketsType + if b.ticketManager == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: ticket manager not implemented") + } + + reqBody := request.GetTicketRequests() + if len(reqBody) == 0 { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: request body had no items") + } + + ticketsResponse, err := b.ticketManager.BulkGetTickets(ctx, request) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: fetching tickets failed: %w", err) + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.TicketsServiceBulkGetTicketsResponse{ + Tickets: ticketsResponse.GetTickets(), + }, nil +} + +func (b *builder) ListTicketSchemas(ctx context.Context, request *v2.TicketsServiceListTicketSchemasRequest) (*v2.TicketsServiceListTicketSchemasResponse, error) { + ctx, span := tracer.Start(ctx, "builder.ListTicketSchemas") + defer span.End() + + start := b.nowFunc() + tt := tasks.ListTicketSchemasType + if b.ticketManager == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: ticket manager not implemented") + } + + retryer := retry.NewRetryer(ctx, retry.RetryConfig{ + MaxAttempts: 10, + InitialDelay: 15 * time.Second, + MaxDelay: 0, + }) + + for { + out, nextPageToken, annos, err := b.ticketManager.ListTicketSchemas(ctx, &pagination.Token{ + Size: int(request.PageSize), + Token: request.PageToken, + }) + if err == nil { + if request.PageToken != "" && request.PageToken == nextPageToken { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: listing ticket schemas failed: next page token is the same as the current page token. this is most likely a connector bug") + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.TicketsServiceListTicketSchemasResponse{ + List: out, + NextPageToken: nextPageToken, + Annotations: annos, + }, nil + } + if retryer.ShouldWaitAndRetry(ctx, err) { + continue + } + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: listing ticket schemas failed: %w", err) + } +} + +func (b *builder) CreateTicket(ctx context.Context, request *v2.TicketsServiceCreateTicketRequest) (*v2.TicketsServiceCreateTicketResponse, error) { + ctx, span := tracer.Start(ctx, "builder.CreateTicket") + defer span.End() + + start := b.nowFunc() + tt := tasks.CreateTicketType + if b.ticketManager == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: ticket manager not implemented") + } + + reqBody := request.GetRequest() + if reqBody == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: request body is nil") + } + cTicket := &v2.Ticket{ + DisplayName: reqBody.GetDisplayName(), + Description: reqBody.GetDescription(), + Status: reqBody.GetStatus(), + Labels: reqBody.GetLabels(), + CustomFields: reqBody.GetCustomFields(), + RequestedFor: reqBody.GetRequestedFor(), + } + + ticket, annos, err := b.ticketManager.CreateTicket(ctx, cTicket, request.GetSchema()) + var resp *v2.TicketsServiceCreateTicketResponse + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + if ticket != nil { + resp = &v2.TicketsServiceCreateTicketResponse{ + Ticket: ticket, + Annotations: annos, + } + } + return resp, fmt.Errorf("error: creating ticket failed: %w", err) + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.TicketsServiceCreateTicketResponse{ + Ticket: ticket, + Annotations: annos, + }, nil +} + +func (b *builder) GetTicket(ctx context.Context, request *v2.TicketsServiceGetTicketRequest) (*v2.TicketsServiceGetTicketResponse, error) { + ctx, span := tracer.Start(ctx, "builder.GetTicket") + defer span.End() + + start := b.nowFunc() + tt := tasks.GetTicketType + if b.ticketManager == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: ticket manager not implemented") + } + + var resp *v2.TicketsServiceGetTicketResponse + ticket, annos, err := b.ticketManager.GetTicket(ctx, request.GetId()) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + if ticket != nil { + resp = &v2.TicketsServiceGetTicketResponse{ + Ticket: ticket, + Annotations: annos, + } + } + return resp, fmt.Errorf("error: getting ticket failed: %w", err) + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.TicketsServiceGetTicketResponse{ + Ticket: ticket, + Annotations: annos, + }, nil +} + +func (b *builder) GetTicketSchema(ctx context.Context, request *v2.TicketsServiceGetTicketSchemaRequest) (*v2.TicketsServiceGetTicketSchemaResponse, error) { + ctx, span := tracer.Start(ctx, "builder.GetTicketSchema") + defer span.End() + + start := b.nowFunc() + tt := tasks.GetTicketSchemaType + if b.ticketManager == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: ticket manager not implemented") + } + + ticketSchema, annos, err := b.ticketManager.GetTicketSchema(ctx, request.GetId()) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: getting ticket metadata failed: %w", err) + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.TicketsServiceGetTicketSchemaResponse{ + Schema: ticketSchema, + Annotations: annos, + }, nil +} + +func (b *builder) addTicketManager(_ context.Context, in interface{}) error { + if ticketManager, ok := in.(TicketManagerLimited); ok { + if b.ticketManager != nil { + return fmt.Errorf("error: cannot set multiple ticket managers") + } + b.ticketManager = ticketManager + } + return nil +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/field/decode_hooks.go b/vendor/github.com/conductorone/baton-sdk/pkg/field/decode_hooks.go new file mode 100644 index 00000000..202ebb56 --- /dev/null +++ b/vendor/github.com/conductorone/baton-sdk/pkg/field/decode_hooks.go @@ -0,0 +1,171 @@ +package field + +import ( + "encoding/base64" + "fmt" + "net/url" + "os" + "reflect" + "strings" + + "github.com/mitchellh/mapstructure" +) + +type DecodeHookOption func(*decodeHookConfig) + +type decodeHookConfig struct { + hookFuncs []mapstructure.DecodeHookFunc +} + +// ComposeDecodeHookFunc returns a mapstructure.DecodeHookFunc that composes +// the default hook functions with any additional hook functions configured. +func ComposeDecodeHookFunc(opts ...DecodeHookOption) mapstructure.DecodeHookFunc { + config := &decodeHookConfig{ + hookFuncs: []mapstructure.DecodeHookFunc{ + // default hook functions used by viper + mapstructure.StringToTimeDurationHookFunc(), + StringToSliceHookFunc(","), + }, + } + for _, opt := range opts { + opt(config) + } + return mapstructure.ComposeDecodeHookFunc(config.hookFuncs...) +} + +func WithAdditionalDecodeHooks(funcs ...mapstructure.DecodeHookFunc) DecodeHookOption { + return func(c *decodeHookConfig) { + c.hookFuncs = append(c.hookFuncs, funcs...) + } +} + +// FileUploadDecodeHook returns a mapstructure.DecodeHookFunc that automatically +// converts string values to []byte for file upload fields, supporting: +// 1. File paths (reads file content) +// 2. Data URLs of JSON with base64 encoding (data:application/json;base64,) +// 3. Raw base64 content +// 4. Raw unencoded content. +func FileUploadDecodeHook(readFromPath bool) mapstructure.DecodeHookFunc { + return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + // Only apply to string -> []byte conversions + if f.Kind() != reflect.String || t.Kind() != reflect.Slice || t.Elem().Kind() != reflect.Uint8 { + return data, nil + } + + str, ok := data.(string) + if !ok { + return data, nil + } + + if readFromPath { + return getFileContentFromPath(str) + } + + return parseFileContent(str) + } +} + +// getFileContentFromPath returns the file content from a path. +func getFileContentFromPath(path string) ([]byte, error) { + if path == "" { + // don't error if the path is empty, leave that to the field validation rules + return []byte{}, nil + } + + // Check if the file exists + fileInfo, err := os.Stat(path) + if err != nil { + return nil, fmt.Errorf("cannot access file: %w", err) + } + + // Check file size limit (2MB) + maxFileSize := 2 * 1024 * 1024 + if fileInfo.Size() > int64(maxFileSize) { + return nil, fmt.Errorf("file too large: %d bytes exceeds limit of %d bytes", fileInfo.Size(), maxFileSize) + } + + // Read the file + content, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("error reading file: %w", err) + } + return content, nil +} + +// parseFileContent returns the file upload content from a string field value. +func parseFileContent(data string) ([]byte, error) { + if data == "" { + // don't error if the data is empty, leave that to the field validation rules + return []byte{}, nil + } + + // Check if it's a data URL first + if strings.HasPrefix(data, "data:") { + return parseJSONBase64DataURL(data) + } + + // Check if it's a base64 encoded string + if decoded, err := base64.StdEncoding.DecodeString(data); err == nil { + return decoded, nil + } + + // Return the content as-is + return []byte(data), nil +} + +// parseJSONBase64DataURL parses a data URL and returns the decoded content. +// Errors if the data is not MIME type application/json and base64 encoded. +func parseJSONBase64DataURL(dataURL string) ([]byte, error) { + parsedURL, err := url.Parse(dataURL) + if err != nil { + return nil, fmt.Errorf("invalid data URL: %w", err) + } + + if parsedURL.Scheme != "data" { + return nil, fmt.Errorf("expected data URL scheme, got: %s", parsedURL.Scheme) + } + + // Split the data URL into media type and data + parts := strings.SplitN(parsedURL.Opaque, ",", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("invalid data URL format: missing comma separator") + } + mediaType := parts[0] + data := parts[1] + + // Check if it's base64 encoded and MIME type application/json + if !strings.HasSuffix(mediaType, ";base64") { + return nil, fmt.Errorf("expected base64 data, got: %s", mediaType) + } + if !strings.HasPrefix(mediaType, "application/json") { + return nil, fmt.Errorf("expected MIME type application/json, got: %s", mediaType) + } + + decoded, err := base64.StdEncoding.DecodeString(data) + if err != nil { + return nil, fmt.Errorf("failed to decode base64 data: %w", err) + } + return decoded, nil +} + +// StringToSliceHookFunc returns a DecodeHookFunc that converts +// string to []string by splitting on the given sep. +// Note: this differs from mapstructure.StringToSliceHookFunc only in that it +// skips cases when the target type is []uint8 (ie []byte). +func StringToSliceHookFunc(sep string) mapstructure.DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}) (interface{}, error) { + if f.Kind() != reflect.String || t.Kind() != reflect.Slice || t.Elem().Kind() == reflect.Uint8 { + return data, nil + } + + raw := data.(string) + if raw == "" { + return []string{}, nil + } + + return strings.Split(raw, sep), nil + } +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/field/field_group.go b/vendor/github.com/conductorone/baton-sdk/pkg/field/field_group.go new file mode 100644 index 00000000..ed54a317 --- /dev/null +++ b/vendor/github.com/conductorone/baton-sdk/pkg/field/field_group.go @@ -0,0 +1,16 @@ +package field + +type SchemaFieldGroup struct { + Name string + DisplayName string + HelpText string + Fields []SchemaField +} + +func WithFieldGroups(fieldGroups []SchemaFieldGroup) configOption { + return func(c Configuration) Configuration { + c.FieldGroups = fieldGroups + + return c + } +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/field/fields.go b/vendor/github.com/conductorone/baton-sdk/pkg/field/fields.go index 5aca646d..36966dfe 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/field/fields.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/field/fields.go @@ -74,6 +74,9 @@ type SchemaField struct { ConnectorConfig connectorConfig WasReExported bool + + // Groups + FieldGroups []SchemaFieldGroup } type SchemaTypes interface { @@ -213,6 +216,27 @@ func StringField(name string, optional ...fieldOption) SchemaField { return field } +func FileUploadField(name string, bonusStrings []string, optional ...fieldOption) SchemaField { + field := SchemaField{ + FieldName: name, + Variant: StringVariant, + DefaultValue: "", + ExportTarget: ExportTargetGUI, + Rules: FieldRule{}, + SyncerConfig: syncerConfig{}, + ConnectorConfig: connectorConfig{ + FieldType: FileUpload, + BonusStrings: bonusStrings, + }, + } + + for _, o := range optional { + field = o(field) + } + + return field +} + func IntField(name string, optional ...fieldOption) SchemaField { field := SchemaField{ FieldName: name, diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/field/marshal.go b/vendor/github.com/conductorone/baton-sdk/pkg/field/marshal.go index a143d98a..cd290328 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/field/marshal.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/field/marshal.go @@ -36,6 +36,8 @@ func (c *Configuration) Marshal() ([]byte, error) { } func (c Configuration) marshal() (*v1_conf.Configuration, error) { + var err error + conf := &v1_conf.Configuration{ DisplayName: c.DisplayName, HelpUrl: c.HelpUrl, @@ -46,145 +48,205 @@ func (c Configuration) marshal() (*v1_conf.Configuration, error) { RequiresExternalConnector: c.RequiresExternalConnector, } + // Fields + conf.Fields, conf.Constraints, err = mapFieldsAndConstraints(c.Fields, c.Constraints) + if err != nil { + return nil, fmt.Errorf("failed to convert fields and constraints to v1: %w", err) + } + + fieldGroups := make([]*v1_conf.FieldGroup, 0, len(c.FieldGroups)) + for _, group := range c.FieldGroups { + fieldGroups = append(fieldGroups, fieldGroupToV1(group)) + } + conf.FieldGroups = fieldGroups + + return conf, nil +} + +func fieldGroupToV1(fg SchemaFieldGroup) *v1_conf.FieldGroup { + fieldGroupV1 := &v1_conf.FieldGroup{ + Name: fg.Name, + DisplayName: fg.DisplayName, + HelpText: fg.HelpText, + } + + fieldGroupV1.Fields = make([]string, 0, len(fg.Fields)) + for _, f := range fg.Fields { + fieldGroupV1.Fields = append(fieldGroupV1.Fields, f.FieldName) + } + + return fieldGroupV1 +} + +func mapFieldsAndConstraints(fields []SchemaField, constraints []SchemaFieldRelationship) ([]*v1_conf.Field, []*v1_conf.Constraint, error) { + resultFields := make([]*v1_conf.Field, 0, len(fields)) + resultConstraints := make([]*v1_conf.Constraint, 0, len(constraints)) + ignore := make(map[string]struct{}) - for _, f := range c.Fields { + for _, f := range fields { if f.ExportTarget != ExportTargetGUI && f.ExportTarget != ExportTargetOps { ignore[f.FieldName] = struct{}{} continue } - field := v1_conf.Field{ - Name: f.FieldName, - DisplayName: f.ConnectorConfig.DisplayName, - Description: f.Description, - Placeholder: f.ConnectorConfig.Placeholder, - IsRequired: f.Required, - IsOps: f.ExportTarget == ExportTargetOps, - IsSecret: f.Secret, - } - - switch f.Variant { - case IntVariant: - intField := &v1_conf.IntField{Rules: f.Rules.i} - d, err := GetDefaultValue[int](f) - if err != nil { - return nil, err - } - if d != nil { - intField.DefaultValue = int64(*d) - } - field.Field = &v1_conf.Field_IntField{IntField: intField} + fieldv1, err := schemaFieldToV1(f) + if err != nil { + return nil, nil, fmt.Errorf("failed to convert field '%s' to v1: %w", f.FieldName, err) + } - case BoolVariant: - boolField := &v1_conf.BoolField{Rules: f.Rules.b} - d, err := GetDefaultValue[bool](f) - if err != nil { - return nil, err - } - if d != nil { - boolField.DefaultValue = *d - } - field.Field = &v1_conf.Field_BoolField{BoolField: boolField} - case StringSliceVariant: - stringSliceField := &v1_conf.StringSliceField{Rules: f.Rules.ss} - d, err := GetDefaultValue[[]string](f) - if err != nil { - return nil, err - } - if d != nil { - stringSliceField.DefaultValue = *d - } - field.Field = &v1_conf.Field_StringSliceField{StringSliceField: stringSliceField} - case StringMapVariant: - stringMapField := &v1_conf.StringMapField{Rules: f.Rules.sm} - d, err := GetDefaultValue[map[string]any](f) - if err != nil { - return nil, err - } - if d != nil { - // Convert map[string]any to map[string]*anypb.Any - anyMap := make(map[string]*anypb.Any) - for k, v := range *d { - // Convert the value to a structpb.Value - value, err := structpb.NewValue(v) - if err != nil { - return nil, fmt.Errorf("failed to convert map value to structpb.Value: %w", err) - } - anyValue, err := anypb.New(value) - if err != nil { - return nil, fmt.Errorf("failed to convert structpb.Value to Any: %w", err) - } - anyMap[k] = anyValue - } - stringMapField.DefaultValue = anyMap - } - field.Field = &v1_conf.Field_StringMapField{StringMapField: stringMapField} - case StringVariant: - stringField := &v1_conf.StringField{Rules: f.Rules.s} - d, err := GetDefaultValue[string](f) - if err != nil { - return nil, err - } - if d != nil { - stringField.DefaultValue = *d - } + resultFields = append(resultFields, fieldv1) + } - switch f.ConnectorConfig.FieldType { - case Text: - stringField.Type = v1_conf.StringFieldType_STRING_FIELD_TYPE_TEXT_UNSPECIFIED - case Randomize: - stringField.Type = v1_conf.StringFieldType_STRING_FIELD_TYPE_RANDOM - case OAuth2: - stringField.Type = v1_conf.StringFieldType_STRING_FIELD_TYPE_OAUTH2 - case ConnectorDerivedOptions: - stringField.Type = v1_conf.StringFieldType_STRING_FIELD_TYPE_CONNECTOR_DERIVED_OPTIONS - case FileUpload: - stringField.Type = v1_conf.StringFieldType_STRING_FIELD_TYPE_FILE_UPLOAD - stringField.AllowedExtensions = f.ConnectorConfig.BonusStrings - default: - return nil, fmt.Errorf("invalid field type: '%s'", f.ConnectorConfig.FieldType) - } + for _, rel := range constraints { + constraint, err := constraintToV1(rel, ignore) + if err != nil { + return nil, nil, fmt.Errorf("failed to convert constraint to v1: %w", err) + } + + if constraint == nil { + continue + } + + resultConstraints = append(resultConstraints, constraint) + } + + return resultFields, resultConstraints, nil +} + +func constraintToV1(rel SchemaFieldRelationship, ignore map[string]struct{}) (*v1_conf.Constraint, error) { + constraint := v1_conf.Constraint{} + + constraintForIgnoredField := false + for _, f := range rel.Fields { + if _, ok := ignore[f.FieldName]; ok { + constraintForIgnoredField = true + break + } + constraint.FieldNames = append(constraint.FieldNames, f.FieldName) + } + if constraintForIgnoredField { + return nil, nil + } - field.Field = &v1_conf.Field_StringField{StringField: stringField} + for _, f := range rel.ExpectedFields { + if _, ok := ignore[f.FieldName]; ok { + constraintForIgnoredField = true + break } - conf.Fields = append(conf.Fields, &field) + constraint.SecondaryFieldNames = append(constraint.SecondaryFieldNames, f.FieldName) + } + + if constraintForIgnoredField { + return nil, nil } - for _, rel := range c.Constraints { - constraint := v1_conf.Constraint{} + kind, ok := RelationshipToConstraintKind[rel.Kind] + if !ok { + return nil, fmt.Errorf("invalid constraint kind: %d", rel.Kind) + } + constraint.Kind = kind - contraintForIgnoredField := false - for _, f := range rel.Fields { - if _, ok := ignore[f.FieldName]; ok { - contraintForIgnoredField = true - break - } - constraint.FieldNames = append(constraint.FieldNames, f.FieldName) + return &constraint, nil +} + +func schemaFieldToV1(f SchemaField) (*v1_conf.Field, error) { + field := v1_conf.Field{ + Name: f.FieldName, + DisplayName: f.ConnectorConfig.DisplayName, + Description: f.Description, + Placeholder: f.ConnectorConfig.Placeholder, + IsRequired: f.Required, + IsOps: f.ExportTarget == ExportTargetOps, + IsSecret: f.Secret, + } + + switch f.Variant { + case IntVariant: + intField := &v1_conf.IntField{Rules: f.Rules.i} + d, err := GetDefaultValue[int](f) + if err != nil { + return nil, err } - if contraintForIgnoredField { - continue + if d != nil { + intField.DefaultValue = int64(*d) } - for _, f := range rel.ExpectedFields { - if _, ok := ignore[f.FieldName]; ok { - contraintForIgnoredField = true - break + field.Field = &v1_conf.Field_IntField{IntField: intField} + + case BoolVariant: + boolField := &v1_conf.BoolField{Rules: f.Rules.b} + d, err := GetDefaultValue[bool](f) + if err != nil { + return nil, err + } + if d != nil { + boolField.DefaultValue = *d + } + field.Field = &v1_conf.Field_BoolField{BoolField: boolField} + case StringSliceVariant: + stringSliceField := &v1_conf.StringSliceField{Rules: f.Rules.ss} + d, err := GetDefaultValue[[]string](f) + if err != nil { + return nil, err + } + if d != nil { + stringSliceField.DefaultValue = *d + } + field.Field = &v1_conf.Field_StringSliceField{StringSliceField: stringSliceField} + case StringMapVariant: + stringMapField := &v1_conf.StringMapField{Rules: f.Rules.sm} + d, err := GetDefaultValue[map[string]any](f) + if err != nil { + return nil, err + } + if d != nil { + // Convert map[string]any to map[string]*anypb.Any + anyMap := make(map[string]*anypb.Any) + for k, v := range *d { + // Convert the value to a structpb.Value + value, err := structpb.NewValue(v) + if err != nil { + return nil, fmt.Errorf("failed to convert map value to structpb.Value: %w", err) + } + anyValue, err := anypb.New(value) + if err != nil { + return nil, fmt.Errorf("failed to convert structpb.Value to Any: %w", err) + } + anyMap[k] = anyValue } - constraint.SecondaryFieldNames = append(constraint.SecondaryFieldNames, f.FieldName) + stringMapField.DefaultValue = anyMap } - - if contraintForIgnoredField { - continue + field.Field = &v1_conf.Field_StringMapField{StringMapField: stringMapField} + case StringVariant: + stringField := &v1_conf.StringField{Rules: f.Rules.s} + d, err := GetDefaultValue[string](f) + if err != nil { + return nil, err + } + if d != nil { + stringField.DefaultValue = *d } - kind, ok := RelationshipToConstraintKind[rel.Kind] - if !ok { - return nil, fmt.Errorf("invalid constraint kind: %d", rel.Kind) + switch f.ConnectorConfig.FieldType { + case Text: + stringField.Type = v1_conf.StringFieldType_STRING_FIELD_TYPE_TEXT_UNSPECIFIED + case Randomize: + stringField.Type = v1_conf.StringFieldType_STRING_FIELD_TYPE_RANDOM + case OAuth2: + stringField.Type = v1_conf.StringFieldType_STRING_FIELD_TYPE_OAUTH2 + case ConnectorDerivedOptions: + stringField.Type = v1_conf.StringFieldType_STRING_FIELD_TYPE_CONNECTOR_DERIVED_OPTIONS + case FileUpload: + stringField.Type = v1_conf.StringFieldType_STRING_FIELD_TYPE_FILE_UPLOAD + stringField.AllowedExtensions = f.ConnectorConfig.BonusStrings + default: + return nil, fmt.Errorf("invalid field type: '%s'", f.ConnectorConfig.FieldType) } - constraint.Kind = kind - conf.Constraints = append(conf.Constraints, &constraint) + field.Field = &v1_conf.Field_StringField{StringField: stringField} + default: + return nil, fmt.Errorf("invalid variant: '%s'", f.Variant) } - return conf, nil + return &field, nil } diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/field/struct.go b/vendor/github.com/conductorone/baton-sdk/pkg/field/struct.go index 6e614e0d..4ea8b27d 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/field/struct.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/field/struct.go @@ -10,6 +10,7 @@ type Configuration struct { IsDirectory bool SupportsExternalResources bool RequiresExternalConnector bool + FieldGroups []SchemaFieldGroup } type configOption func(Configuration) Configuration diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/sdk/version.go b/vendor/github.com/conductorone/baton-sdk/pkg/sdk/version.go index 4bc7345e..d52155b3 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/sdk/version.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/sdk/version.go @@ -1,3 +1,3 @@ package sdk -const Version = "v0.4.7" +const Version = "v0.4.12" diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/session/grpc_session.go b/vendor/github.com/conductorone/baton-sdk/pkg/session/grpc_session.go index 70b8a868..1635017c 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/session/grpc_session.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/session/grpc_session.go @@ -15,7 +15,7 @@ import ( v1 "github.com/conductorone/baton-sdk/pb/c1/connectorapi/baton/v1" "github.com/conductorone/baton-sdk/pkg/sdk" - "github.com/conductorone/baton-sdk/pkg/types" + "github.com/conductorone/baton-sdk/pkg/types/sessions" dpop_grpc "github.com/conductorone/dpop/integrations/dpop_grpc" "github.com/conductorone/dpop/pkg/dpop" "github.com/go-jose/go-jose/v4" @@ -35,7 +35,7 @@ type GRPCSessionCache struct { // It reuses an existing access token and DPoP key instead of performing a new authentication round. // It reads the session service address from the BATON_SESSION_SERVICE_ADDR environment variable, // defaulting to "localhost:50051" if not set. -func NewGRPCSessionClient(ctx context.Context, accessToken string, dpopKey *jose.JSONWebKey, opt ...types.SessionCacheConstructorOption) (v1.BatonSessionServiceClient, error) { +func NewGRPCSessionClient(ctx context.Context, accessToken string, dpopKey *jose.JSONWebKey, opt ...sessions.SessionStoreConstructorOption) (v1.BatonSessionServiceClient, error) { // Apply constructor options for _, option := range opt { var err error @@ -123,7 +123,7 @@ func (s *staticTokenSource) Token() (*oauth2.Token, error) { // These functions are no longer needed since we're reusing existing credentials // NewGRPCSessionCache creates a new gRPC session cache instance. -func NewGRPCSessionCache(ctx context.Context, client v1.BatonSessionServiceClient, opt ...types.SessionCacheConstructorOption) (types.SessionCache, error) { +func NewGRPCSessionCache(ctx context.Context, client v1.BatonSessionServiceClient, opt ...sessions.SessionStoreConstructorOption) (sessions.SessionStore, error) { // Apply constructor options for _, option := range opt { var err error @@ -139,7 +139,7 @@ func NewGRPCSessionCache(ctx context.Context, client v1.BatonSessionServiceClien } // Get retrieves a value from the cache by key. -func (g *GRPCSessionCache) Get(ctx context.Context, key string, opt ...types.SessionCacheOption) ([]byte, bool, error) { +func (g *GRPCSessionCache) Get(ctx context.Context, key string, opt ...sessions.SessionStoreOption) ([]byte, bool, error) { bag, err := applyOptions(ctx, opt...) if err != nil { return nil, false, err @@ -167,7 +167,7 @@ func (g *GRPCSessionCache) Get(ctx context.Context, key string, opt ...types.Ses } // GetMany retrieves multiple values from the cache by keys. -func (g *GRPCSessionCache) GetMany(ctx context.Context, keys []string, opt ...types.SessionCacheOption) (map[string][]byte, error) { +func (g *GRPCSessionCache) GetMany(ctx context.Context, keys []string, opt ...sessions.SessionStoreOption) (map[string][]byte, error) { bag, err := applyOptions(ctx, opt...) if err != nil { return nil, err @@ -212,7 +212,7 @@ func (g *GRPCSessionCache) GetMany(ctx context.Context, keys []string, opt ...ty } // Set stores a value in the cache with the given key. -func (g *GRPCSessionCache) Set(ctx context.Context, key string, value []byte, opt ...types.SessionCacheOption) error { +func (g *GRPCSessionCache) Set(ctx context.Context, key string, value []byte, opt ...sessions.SessionStoreOption) error { bag, err := applyOptions(ctx, opt...) if err != nil { return err @@ -237,7 +237,7 @@ func (g *GRPCSessionCache) Set(ctx context.Context, key string, value []byte, op } // SetMany stores multiple values in the cache. -func (g *GRPCSessionCache) SetMany(ctx context.Context, values map[string][]byte, opt ...types.SessionCacheOption) error { +func (g *GRPCSessionCache) SetMany(ctx context.Context, values map[string][]byte, opt ...sessions.SessionStoreOption) error { bag, err := applyOptions(ctx, opt...) if err != nil { return err @@ -267,7 +267,7 @@ func (g *GRPCSessionCache) SetMany(ctx context.Context, values map[string][]byte } // Delete removes a value from the cache by key. -func (g *GRPCSessionCache) Delete(ctx context.Context, key string, opt ...types.SessionCacheOption) error { +func (g *GRPCSessionCache) Delete(ctx context.Context, key string, opt ...sessions.SessionStoreOption) error { bag, err := applyOptions(ctx, opt...) if err != nil { return err @@ -291,7 +291,7 @@ func (g *GRPCSessionCache) Delete(ctx context.Context, key string, opt ...types. } // Clear removes all values from the cache. -func (g *GRPCSessionCache) Clear(ctx context.Context, opt ...types.SessionCacheOption) error { +func (g *GRPCSessionCache) Clear(ctx context.Context, opt ...sessions.SessionStoreOption) error { bag, err := applyOptions(ctx, opt...) if err != nil { return err @@ -313,7 +313,7 @@ func (g *GRPCSessionCache) Clear(ctx context.Context, opt ...types.SessionCacheO // Note: The gRPC service doesn't have a GetAll method, so we'll need to implement this // by getting all keys first and then using GetMany. This is a limitation of the current // gRPC service definition. -func (g *GRPCSessionCache) GetAll(ctx context.Context, opt ...types.SessionCacheOption) (map[string][]byte, error) { +func (g *GRPCSessionCache) GetAll(ctx context.Context, opt ...sessions.SessionStoreOption) (map[string][]byte, error) { bag, err := applyOptions(ctx, opt...) if err != nil { return nil, err @@ -367,7 +367,7 @@ func (g *GRPCSessionCache) GetAll(ctx context.Context, opt ...types.SessionCache } // Close performs any necessary cleanup when the cache is no longer needed. -func (g *GRPCSessionCache) Close(ctx context.Context) error { +func (g *GRPCSessionCache) CloseStore(ctx context.Context) error { // No cleanup needed for gRPC client return nil } diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/session/json.go b/vendor/github.com/conductorone/baton-sdk/pkg/session/json.go index 285bbac3..ba74f5b5 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/session/json.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/session/json.go @@ -4,10 +4,10 @@ import ( "context" "encoding/json" - "github.com/conductorone/baton-sdk/pkg/types" + "github.com/conductorone/baton-sdk/pkg/types/sessions" ) -func GetManyJSON[T any](ctx context.Context, keys []string, opt ...types.SessionCacheOption) (map[string]T, error) { +func GetManyJSON[T any](ctx context.Context, keys []string, opt ...sessions.SessionStoreOption) (map[string]T, error) { cache, err := GetSession(ctx) if err != nil { return nil, err @@ -32,7 +32,7 @@ func GetManyJSON[T any](ctx context.Context, keys []string, opt ...types.Session return result, nil } -func SetManyJSON[T any](ctx context.Context, items map[string]T, opt ...types.SessionCacheOption) error { +func SetManyJSON[T any](ctx context.Context, items map[string]T, opt ...sessions.SessionStoreOption) error { cache, err := GetSession(ctx) if err != nil { return err @@ -52,7 +52,7 @@ func SetManyJSON[T any](ctx context.Context, items map[string]T, opt ...types.Se return cache.SetMany(ctx, bytesMap, opt...) } -func GetJSON[T any](ctx context.Context, key string, opt ...types.SessionCacheOption) (T, bool, error) { +func GetJSON[T any](ctx context.Context, key string, opt ...sessions.SessionStoreOption) (T, bool, error) { var zero T cache, err := GetSession(ctx) if err != nil { @@ -75,7 +75,7 @@ func GetJSON[T any](ctx context.Context, key string, opt ...types.SessionCacheOp return item, true, nil } -func SetJSON[T any](ctx context.Context, key string, item T, opt ...types.SessionCacheOption) error { +func SetJSON[T any](ctx context.Context, key string, item T, opt ...sessions.SessionStoreOption) error { cache, err := GetSession(ctx) if err != nil { return err @@ -91,7 +91,7 @@ func SetJSON[T any](ctx context.Context, key string, item T, opt ...types.Sessio return cache.Set(ctx, key, bytes, opt...) } -func DeleteJSON(ctx context.Context, key string, opt ...types.SessionCacheOption) error { +func DeleteJSON(ctx context.Context, key string, opt ...sessions.SessionStoreOption) error { cache, err := GetSession(ctx) if err != nil { return err @@ -100,7 +100,7 @@ func DeleteJSON(ctx context.Context, key string, opt ...types.SessionCacheOption return cache.Delete(ctx, key, opt...) } -func ClearJSON(ctx context.Context, opt ...types.SessionCacheOption) error { +func ClearJSON(ctx context.Context, opt ...sessions.SessionStoreOption) error { cache, err := GetSession(ctx) if err != nil { return err @@ -109,7 +109,7 @@ func ClearJSON(ctx context.Context, opt ...types.SessionCacheOption) error { return cache.Clear(ctx, opt...) } -func GetAllJSON[T any](ctx context.Context, opt ...types.SessionCacheOption) (map[string]T, error) { +func GetAllJSON[T any](ctx context.Context, opt ...sessions.SessionStoreOption) (map[string]T, error) { cache, err := GetSession(ctx) if err != nil { return nil, err diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/session/memory.go b/vendor/github.com/conductorone/baton-sdk/pkg/session/memory.go index 6aa5a4b5..b924572f 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/session/memory.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/session/memory.go @@ -7,7 +7,7 @@ import ( "sync" "time" - "github.com/conductorone/baton-sdk/pkg/types" + "github.com/conductorone/baton-sdk/pkg/types/sessions" ) // MemorySessionCache implements SessionCache interface using an in-memory store with TTL. @@ -18,12 +18,12 @@ type MemorySessionCache struct { } // NewMemorySessionCache creates a new in-memory session cache with default TTL of 1 hour. -func NewMemorySessionCache(ctx context.Context, opt ...types.SessionCacheConstructorOption) (types.SessionCache, error) { +func NewMemorySessionCache(ctx context.Context, opt ...sessions.SessionStoreConstructorOption) (sessions.SessionStore, error) { return NewMemorySessionCacheWithTTL(ctx, time.Hour, opt...) } // NewMemorySessionCacheWithTTL creates a new in-memory session cache with custom TTL. -func NewMemorySessionCacheWithTTL(ctx context.Context, ttl time.Duration, opt ...types.SessionCacheConstructorOption) (types.SessionCache, error) { +func NewMemorySessionCacheWithTTL(ctx context.Context, ttl time.Duration, opt ...sessions.SessionStoreConstructorOption) (sessions.SessionStore, error) { // Apply constructor options for _, option := range opt { var err error @@ -40,7 +40,7 @@ func NewMemorySessionCacheWithTTL(ctx context.Context, ttl time.Duration, opt .. } // Get retrieves a value from the cache by key. -func (m *MemorySessionCache) Get(ctx context.Context, key string, opt ...types.SessionCacheOption) ([]byte, bool, error) { +func (m *MemorySessionCache) Get(ctx context.Context, key string, opt ...sessions.SessionStoreOption) ([]byte, bool, error) { bag, err := applyOptions(ctx, opt...) if err != nil { return nil, false, err @@ -68,7 +68,7 @@ func (m *MemorySessionCache) Get(ctx context.Context, key string, opt ...types.S } // Set stores a value in the cache with the given key. -func (m *MemorySessionCache) Set(ctx context.Context, key string, value []byte, opt ...types.SessionCacheOption) error { +func (m *MemorySessionCache) Set(ctx context.Context, key string, value []byte, opt ...sessions.SessionStoreOption) error { bag, err := applyOptions(ctx, opt...) if err != nil { return err @@ -93,7 +93,7 @@ func (m *MemorySessionCache) Set(ctx context.Context, key string, value []byte, } // Delete removes a value from the cache by key. -func (m *MemorySessionCache) Delete(ctx context.Context, key string, opt ...types.SessionCacheOption) error { +func (m *MemorySessionCache) Delete(ctx context.Context, key string, opt ...sessions.SessionStoreOption) error { bag, err := applyOptions(ctx, opt...) if err != nil { return err @@ -113,7 +113,7 @@ func (m *MemorySessionCache) Delete(ctx context.Context, key string, opt ...type } // Clear removes all values from the cache. -func (m *MemorySessionCache) Clear(ctx context.Context, opt ...types.SessionCacheOption) error { +func (m *MemorySessionCache) Clear(ctx context.Context, opt ...sessions.SessionStoreOption) error { bag, err := applyOptions(ctx, opt...) if err != nil { return err @@ -127,7 +127,7 @@ func (m *MemorySessionCache) Clear(ctx context.Context, opt ...types.SessionCach } // GetAll returns all key-value pairs. -func (m *MemorySessionCache) GetAll(ctx context.Context, opt ...types.SessionCacheOption) (map[string][]byte, error) { +func (m *MemorySessionCache) GetAll(ctx context.Context, opt ...sessions.SessionStoreOption) (map[string][]byte, error) { bag, err := applyOptions(ctx, opt...) if err != nil { return nil, err @@ -155,7 +155,7 @@ func (m *MemorySessionCache) GetAll(ctx context.Context, opt ...types.SessionCac } // GetMany retrieves multiple values from the cache by keys. -func (m *MemorySessionCache) GetMany(ctx context.Context, keys []string, opt ...types.SessionCacheOption) (map[string][]byte, error) { +func (m *MemorySessionCache) GetMany(ctx context.Context, keys []string, opt ...sessions.SessionStoreOption) (map[string][]byte, error) { bag, err := applyOptions(ctx, opt...) if err != nil { return nil, err @@ -183,7 +183,7 @@ func (m *MemorySessionCache) GetMany(ctx context.Context, keys []string, opt ... } // SetMany stores multiple values in the cache. -func (m *MemorySessionCache) SetMany(ctx context.Context, values map[string][]byte, opt ...types.SessionCacheOption) error { +func (m *MemorySessionCache) SetMany(ctx context.Context, values map[string][]byte, opt ...sessions.SessionStoreOption) error { // Apply options to get syncID bag, err := applyOptions(ctx, opt...) if err != nil { @@ -211,7 +211,7 @@ func (m *MemorySessionCache) SetMany(ctx context.Context, values map[string][]by } // Close performs any necessary cleanup when the cache is no longer needed. -func (m *MemorySessionCache) Close(ctx context.Context) error { +func (m *MemorySessionCache) CloseStore(ctx context.Context) error { m.mu.Lock() defer m.mu.Unlock() // Clear all data diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/session/session.go b/vendor/github.com/conductorone/baton-sdk/pkg/session/session.go index 92ee1bfc..8b633dbb 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/session/session.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/session/session.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/conductorone/baton-sdk/pkg/types" + "github.com/conductorone/baton-sdk/pkg/types/sessions" ) // KeyPrefixDelimiter is the delimiter used to separate prefixes from keys in the session cache. @@ -12,22 +12,22 @@ const KeyPrefixDelimiter = "::" // GetSession retrieves the session cache instance from the context. // Returns an error if no session cache is found in the context. -func GetSession(ctx context.Context) (types.SessionCache, error) { - if sessionCache, ok := ctx.Value(types.SessionCacheKey{}).(types.SessionCache); ok { +func GetSession(ctx context.Context) (sessions.SessionStore, error) { + if sessionCache, ok := ctx.Value(sessions.SessionStoreKey{}).(sessions.SessionStore); ok { return sessionCache, nil } return nil, fmt.Errorf("no session cache found in context") } -func WithSyncID(syncID string) types.SessionCacheOption { - return func(ctx context.Context, bag *types.SessionCacheBag) error { +func WithSyncID(syncID string) sessions.SessionStoreOption { + return func(ctx context.Context, bag *sessions.SessionStoreBag) error { bag.SyncID = syncID return nil } } -func WithPrefix(prefix string) types.SessionCacheOption { - return func(ctx context.Context, bag *types.SessionCacheBag) error { +func WithPrefix(prefix string) sessions.SessionStoreOption { + return func(ctx context.Context, bag *sessions.SessionStoreBag) error { bag.Prefix = prefix return nil } @@ -35,15 +35,15 @@ func WithPrefix(prefix string) types.SessionCacheOption { // GetSyncIDFromContext retrieves the sync ID from the context, returning empty string if not found. func GetSyncIDFromContext(ctx context.Context) string { - if syncID, ok := ctx.Value(types.SyncIDKey{}).(string); ok { + if syncID, ok := ctx.Value(sessions.SyncIDKey{}).(string); ok { return syncID } return "" } // applyOptions applies session cache options and returns a configured bag. -func applyOptions(ctx context.Context, opt ...types.SessionCacheOption) (*types.SessionCacheBag, error) { - bag := &types.SessionCacheBag{} +func applyOptions(ctx context.Context, opt ...sessions.SessionStoreOption) (*sessions.SessionStoreBag, error) { + bag := &sessions.SessionStoreBag{} for _, option := range opt { err := option(ctx, bag) diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/sync/syncer.go b/vendor/github.com/conductorone/baton-sdk/pkg/sync/syncer.go index c8cbd1f4..be5dbe3a 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/sync/syncer.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/sync/syncer.go @@ -21,6 +21,7 @@ import ( "github.com/conductorone/baton-sdk/pkg/types/entitlement" batonGrant "github.com/conductorone/baton-sdk/pkg/types/grant" "github.com/conductorone/baton-sdk/pkg/types/resource" + "github.com/conductorone/baton-sdk/pkg/types/sessions" mapset "github.com/deckarep/golang-set/v2" "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" "go.opentelemetry.io/otel" @@ -392,7 +393,7 @@ func (s *syncer) Sync(ctx context.Context) error { // Add ActiveSync to context once after we have the syncID if syncID != "" { - ctx = types.SetSyncIDInContext(ctx, syncID) + ctx = sessions.SetSyncIDInContext(ctx, syncID) } span.SetAttributes(attribute.String("sync_id", syncID)) @@ -2730,6 +2731,13 @@ func (s *syncer) Close(ctx context.Context) error { } } + if s.externalResourceReader != nil { + err = s.externalResourceReader.Close() + if err != nil { + return fmt.Errorf("error closing external resource reader: %w", err) + } + } + if s.c1zManager != nil { err = s.c1zManager.SaveC1Z(ctx) if err != nil { diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/synccompactor/compactor.go b/vendor/github.com/conductorone/baton-sdk/pkg/synccompactor/compactor.go index 1d638341..726d58ce 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/synccompactor/compactor.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/synccompactor/compactor.go @@ -128,6 +128,7 @@ func (c *Compactor) Compact(ctx context.Context) (*CompactableSync, error) { ctx, emptyConnector, sync.WithC1ZPath(applied.FilePath), + sync.WithTmpDir(c.tmpDir), sync.WithSyncID(applied.SyncID), sync.WithOnlyExpandGrants(), ) diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/types/resource/resource.go b/vendor/github.com/conductorone/baton-sdk/pkg/types/resource/resource.go index 585d9bea..d91582cb 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/types/resource/resource.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/types/resource/resource.go @@ -337,3 +337,8 @@ func NewSecretResource( return ret, nil } + +type Options struct { + // Session sessions.SessionStore + // SyncID string +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/types/session_cache.go b/vendor/github.com/conductorone/baton-sdk/pkg/types/session_cache.go deleted file mode 100644 index 86d69eb5..00000000 --- a/vendor/github.com/conductorone/baton-sdk/pkg/types/session_cache.go +++ /dev/null @@ -1,58 +0,0 @@ -package types - -import ( - "context" -) - -// SessionCacheKey is the context key for storing the session cache instance. -type SessionCacheKey struct{} - -// SessionCache is an interface for caching session data. -type SessionCache interface { - Get(ctx context.Context, key string, opt ...SessionCacheOption) ([]byte, bool, error) - GetMany(ctx context.Context, keys []string, opt ...SessionCacheOption) (map[string][]byte, error) - Set(ctx context.Context, key string, value []byte, opt ...SessionCacheOption) error - SetMany(ctx context.Context, values map[string][]byte, opt ...SessionCacheOption) error - Delete(ctx context.Context, key string, opt ...SessionCacheOption) error - Clear(ctx context.Context, opt ...SessionCacheOption) error - GetAll(ctx context.Context, opt ...SessionCacheOption) (map[string][]byte, error) - Close(ctx context.Context) error -} - -// SessionCacheOption is a function that modifies a SessionCacheBag. -type SessionCacheOption func(ctx context.Context, bag *SessionCacheBag) error - -// SessionCacheConstructor is a function that creates a SessionCache instance. -type SessionCacheConstructor func(ctx context.Context, opt ...SessionCacheConstructorOption) (SessionCache, error) - -// SessionCacheConstructorOption is a function that modifies the context for session cache construction. -type SessionCacheConstructorOption func(ctx context.Context) (context.Context, error) - -// SessionCacheBag holds the configuration for session cache operations. -type SessionCacheBag struct { - SyncID string - Prefix string -} - -// SyncIDKey is the context key for storing the current sync ID. -type SyncIDKey struct{} - -// WithSyncID returns a SessionCacheOption that sets the sync ID for the operation. -func WithSyncID(syncID string) SessionCacheOption { - return func(ctx context.Context, bag *SessionCacheBag) error { - bag.SyncID = syncID - return nil - } -} - -// GetSyncID retrieves the sync ID from the context, returning empty string if not found. -func GetSyncID(ctx context.Context) string { - if syncID, ok := ctx.Value(SyncIDKey{}).(string); ok { - return syncID - } - return "" -} - -func SetSyncIDInContext(ctx context.Context, syncID string) context.Context { - return context.WithValue(ctx, SyncIDKey{}, syncID) -} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/types/sessions/sessions.go b/vendor/github.com/conductorone/baton-sdk/pkg/types/sessions/sessions.go new file mode 100644 index 00000000..26ae817a --- /dev/null +++ b/vendor/github.com/conductorone/baton-sdk/pkg/types/sessions/sessions.go @@ -0,0 +1,52 @@ +package sessions + +import ( + "context" +) + +type SessionStoreKey struct{} + +type SessionStore interface { + Get(ctx context.Context, key string, opt ...SessionStoreOption) ([]byte, bool, error) + GetMany(ctx context.Context, keys []string, opt ...SessionStoreOption) (map[string][]byte, error) + Set(ctx context.Context, key string, value []byte, opt ...SessionStoreOption) error + SetMany(ctx context.Context, values map[string][]byte, opt ...SessionStoreOption) error + Delete(ctx context.Context, key string, opt ...SessionStoreOption) error + Clear(ctx context.Context, opt ...SessionStoreOption) error + GetAll(ctx context.Context, opt ...SessionStoreOption) (map[string][]byte, error) + CloseStore(ctx context.Context) error +} + +type SessionStoreOption func(ctx context.Context, bag *SessionStoreBag) error + +type SessionStoreConstructor func(ctx context.Context, opt ...SessionStoreConstructorOption) (SessionStore, error) + +type SessionStoreConstructorOption func(ctx context.Context) (context.Context, error) + +type SessionStoreBag struct { + SyncID string + Prefix string +} + +// SyncIDKey is the context key for storing the current sync ID. +type SyncIDKey struct{} + +// WithSyncID returns a SessionCacheOption that sets the sync ID for the operation. +func WithSyncID(syncID string) SessionStoreOption { + return func(ctx context.Context, bag *SessionStoreBag) error { + bag.SyncID = syncID + return nil + } +} + +// GetSyncID retrieves the sync ID from the context, returning empty string if not found. +func GetSyncID(ctx context.Context) string { + if syncID, ok := ctx.Value(SyncIDKey{}).(string); ok { + return syncID + } + return "" +} + +func SetSyncIDInContext(ctx context.Context, syncID string) context.Context { + return context.WithValue(ctx, SyncIDKey{}, syncID) +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/ugrpc/interceptors.go b/vendor/github.com/conductorone/baton-sdk/pkg/ugrpc/interceptors.go index 1f412c22..a7f046df 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/ugrpc/interceptors.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/ugrpc/interceptors.go @@ -3,7 +3,7 @@ package ugrpc import ( "context" - "github.com/conductorone/baton-sdk/pkg/types" + "github.com/conductorone/baton-sdk/pkg/types/sessions" "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags" @@ -39,8 +39,8 @@ func SessionCacheUnaryInterceptor(serverCtx context.Context) grpc.UnaryServerInt // Propagate session cache from server context to handler context ctx = ContextWithSyncID(ctx, req) - if sessionCache, ok := serverCtx.Value(types.SessionCacheKey{}).(types.SessionCache); ok { - ctx = context.WithValue(ctx, types.SessionCacheKey{}, sessionCache) + if sessionCache, ok := serverCtx.Value(sessions.SessionStoreKey{}).(sessions.SessionStore); ok { + ctx = context.WithValue(ctx, sessions.SessionStoreKey{}, sessionCache) } return handler(ctx, req) @@ -53,8 +53,8 @@ func SessionCacheStreamInterceptor(serverCtx context.Context) grpc.StreamServerI ctx := ss.Context() // Propagate session cache from server context to stream context - if sessionCache, ok := serverCtx.Value(types.SessionCacheKey{}).(types.SessionCache); ok { - ctx = context.WithValue(ctx, types.SessionCacheKey{}, sessionCache) + if sessionCache, ok := serverCtx.Value(sessions.SessionStoreKey{}).(sessions.SessionStore); ok { + ctx = context.WithValue(ctx, sessions.SessionStoreKey{}, sessionCache) } // Create a wrapped stream that handles both session cache and annotation extraction @@ -151,5 +151,5 @@ func ContextWithSyncID(ctx context.Context, req any) context.Context { return ctx } - return context.WithValue(ctx, types.SyncIDKey{}, syncID) + return context.WithValue(ctx, sessions.SyncIDKey{}, syncID) } diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/uotel/config.go b/vendor/github.com/conductorone/baton-sdk/pkg/uotel/config.go index 14082a35..9d107225 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/uotel/config.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/uotel/config.go @@ -251,7 +251,7 @@ func (c *otelConfig) initLogging(ctx context.Context, cc *grpc.ClientConn) (cont return nil, fmt.Errorf("failed to initialize otlp exporter: %w", err) } // TODO(morgabra): Whole bunch of tunables _here_... - processor := log.NewBatchProcessor(exp, log.WithExportInterval(time.Second*5)) + processor := log.NewBatchProcessor(exp, log.WithExportInterval(time.Second*1)) // TODO(morgabra): Whole bunch of tunables ALSO HERE... provider := log.NewLoggerProvider( log.WithResource(res), diff --git a/vendor/modules.txt b/vendor/modules.txt index ecbbd0f9..30958f39 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -178,8 +178,8 @@ github.com/benbjohnson/clock # github.com/cenkalti/backoff/v4 v4.3.0 ## explicit; go 1.18 github.com/cenkalti/backoff/v4 -# github.com/conductorone/baton-sdk v0.4.8 -## explicit; go 1.25 +# github.com/conductorone/baton-sdk v0.4.13 +## explicit; go 1.25.2 github.com/conductorone/baton-sdk/internal/connector github.com/conductorone/baton-sdk/pb/c1/c1z/v1 github.com/conductorone/baton-sdk/pb/c1/config/v1 @@ -232,6 +232,7 @@ github.com/conductorone/baton-sdk/pkg/types github.com/conductorone/baton-sdk/pkg/types/entitlement github.com/conductorone/baton-sdk/pkg/types/grant github.com/conductorone/baton-sdk/pkg/types/resource +github.com/conductorone/baton-sdk/pkg/types/sessions github.com/conductorone/baton-sdk/pkg/types/tasks github.com/conductorone/baton-sdk/pkg/types/ticket github.com/conductorone/baton-sdk/pkg/ugrpc