Skip to content

Commit

Permalink
feat(backend/grpc-connect): kafka connect
Browse files Browse the repository at this point in the history
Implement grcp connect for the kafka-connect service, add mapper for
transforming the kafka connect responses into a valid grpc connect
responses, errors etc

Signed-off-by: Santiago Jimenez Giraldo <[email protected]>
  • Loading branch information
sago2k8 committed Nov 13, 2023
1 parent 9be7b45 commit abbd8a2
Show file tree
Hide file tree
Showing 6 changed files with 448 additions and 14 deletions.
12 changes: 6 additions & 6 deletions backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ go 1.20

require (
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20230830185350-7a34d6557349.1
connectrpc.com/connect v1.11.1
connectrpc.com/connect v1.12.0
connectrpc.com/grpcreflect v1.2.0
github.com/basgys/goxml2json v1.1.0
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869
github.com/bufbuild/protovalidate-go v0.3.1
github.com/carlmjohnson/requests v0.23.5
github.com/cloudhut/common v0.10.0
github.com/cloudhut/connect-client v0.0.0-20230417124247-963e5bcdfee7
github.com/docker/docker v24.0.4+incompatible
Expand All @@ -22,7 +23,8 @@ require (
github.com/go-resty/resty/v2 v2.7.0
github.com/golang/mock v1.6.0
github.com/golang/protobuf v1.5.3
github.com/google/uuid v1.3.0
github.com/google/go-cmp v0.6.0
github.com/google/uuid v1.3.1
github.com/gorilla/schema v1.2.0
github.com/gorilla/websocket v1.5.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0
Expand All @@ -48,15 +50,15 @@ require (
github.com/vmihailenco/msgpack/v5 v5.3.5
github.com/zencoder/go-smile v0.0.0-20220221105746-06ef4fe5fa0a
go.uber.org/zap v1.24.0
go.vallahaye.net/connect-gateway v0.3.0
go.vallahaye.net/connect-gateway v0.3.1
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
golang.org/x/net v0.17.0
golang.org/x/sync v0.3.0
golang.org/x/text v0.13.0
google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97
google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13
google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c
google.golang.org/grpc v1.58.3
google.golang.org/grpc v1.59.0
google.golang.org/protobuf v1.31.0
)

Expand All @@ -68,7 +70,6 @@ require (
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bitly/go-simplejson v0.5.0 // indirect
github.com/carlmjohnson/requests v0.23.5 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cloudflare/circl v1.3.3 // indirect
Expand All @@ -89,7 +90,6 @@ require (
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/cel-go v0.18.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
Expand Down
20 changes: 12 additions & 8 deletions backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
connectrpc.com/connect v1.11.1 h1:dqRwblixqkVh+OFBOOL1yIf1jS/yP0MSJLijRj29bFg=
connectrpc.com/connect v1.11.1/go.mod h1:3AGaO6RRGMx5IKFfqbe3hvK1NqLosFNP2BxDYTPmNPo=
connectrpc.com/connect v1.12.0 h1:HwKdOY0lGhhoHdsza+hW55aqHEC64pYpObRNoAgn70g=
connectrpc.com/connect v1.12.0/go.mod h1:3AGaO6RRGMx5IKFfqbe3hvK1NqLosFNP2BxDYTPmNPo=
connectrpc.com/grpcreflect v1.2.0 h1:Q6og1S7HinmtbEuBvARLNwYmTbhEGRpHDhqrPNlmK+U=
connectrpc.com/grpcreflect v1.2.0/go.mod h1:nwSOKmE8nU5u/CidgHtPYk1PFI3U9ignz7iDMxOYkSY=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
Expand Down Expand Up @@ -88,6 +90,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/birdayz/connect-gateway v0.0.0-20231105183723-cad0472a8a02 h1:EdFLsz/XVlTjg5U01gWpwBNQTwldEiu47QmdYGrK7t4=
github.com/birdayz/connect-gateway v0.0.0-20231105183723-cad0472a8a02/go.mod h1:DM9vOUjCkEmV0uAfEFp0Wpx50TkiD4ONdWKHzLkRhBg=
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
Expand Down Expand Up @@ -223,7 +227,7 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
Expand Down Expand Up @@ -273,7 +277,6 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
Expand All @@ -295,8 +298,8 @@ github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 h1:n6vlPhxsA+BW/XsS5+
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
Expand Down Expand Up @@ -658,8 +661,8 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
go.vallahaye.net/connect-gateway v0.3.0 h1:4QJ3+bIctIt+VuyLXcjfMkp8bwqoCSs4yx43QHNjwso=
go.vallahaye.net/connect-gateway v0.3.0/go.mod h1:aspehLu3zuVotIESKfJwzCoV6ViHEXEOieEfQnwDwRM=
go.vallahaye.net/connect-gateway v0.3.1 h1:829VIFcOYgkKJTrRTf/C382MnFF4KNgZwtkCcD48ntw=
go.vallahaye.net/connect-gateway v0.3.1/go.mod h1:aM7ANmNrnY2YC51bHLj9Gi0sRv14HvBZeDPtzOL3uGY=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
Expand Down Expand Up @@ -1049,8 +1052,8 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
Expand Down Expand Up @@ -1092,6 +1095,7 @@ gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
Expand Down
177 changes: 177 additions & 0 deletions backend/pkg/api/connect/service/kafkaconnect/mapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// Copyright 2023 Redpanda Data, Inc.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.md
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0

// Package kafkaconnect implements the KafkaConnect interface for the Connect API.
package kafkaconnect

import (
"fmt"

con "github.com/cloudhut/connect-client"
"golang.org/x/exp/slices"

kafkaconnect "github.com/redpanda-data/console/backend/pkg/connect"
dataplanev1alpha1 "github.com/redpanda-data/console/backend/pkg/protogen/redpanda/api/dataplane/v1alpha1"
)

type mapper struct{}

func (m mapper) connectorsHTTPResponseToProto(httpResponse kafkaconnect.ClusterConnectors, request *dataplanev1alpha1.ListConnectorsRequest) (*dataplanev1alpha1.ListConnectorsResponse, error) {
connectors := make(map[string]*dataplanev1alpha1.ListConnectorsResponse_ConnectorInfoStatus, len(httpResponse.Connectors))

for _, connector := range httpResponse.Connectors {
errors, err := m.connectorErrorsToProto(connector.Errors)
if err != nil {
return nil, fmt.Errorf("failed to map connector error to proto for connector %q: %w", connector.Name, err)
}

connectors[connector.Name] = &dataplanev1alpha1.ListConnectorsResponse_ConnectorInfoStatus{
HolisticState: m.holisticStateToProto(connector.Status),
Errors: errors,
}

if slices.Contains(request.Expand, "info") {
connectors[connector.Name].Info = &dataplanev1alpha1.ConnectorSpec{
Name: connector.Name,
Type: connector.Type,
Config: connector.Config,
Tasks: m.taskInfoListToProtoInfo(connector.Name, connector.Tasks),
}
}

if slices.Contains(request.Expand, "status") {
connectors[connector.Name].Status = &dataplanev1alpha1.ConnectorStatus{
Name: connector.Name,
Connector: &dataplanev1alpha1.ConnectorStatus_Connector{
State: connector.State,
WorkerId: connector.WorkerID,
},
Tasks: m.taskInfoListToProtoStatus(connector.Tasks),
Type: connector.Type,
Trace: connector.Trace,
}
}
}

return &dataplanev1alpha1.ListConnectorsResponse{
Connectors: connectors,
ClusterAddress: httpResponse.ClusterAddress,
ClusterName: httpResponse.ClusterName,
}, nil
}

func (m mapper) taskInfoListToProtoInfo(connectorName string, taskInfoList []kafkaconnect.ClusterConnectorTaskInfo) []*dataplanev1alpha1.TaskInfo {
tasks := make([]*dataplanev1alpha1.TaskInfo, len(taskInfoList))
for i, task := range taskInfoList {
tasks[i] = m.taskToProto(connectorName, task.TaskID)
}
return tasks
}

func (m mapper) connectorTaskIDToProto(connectorName string, taskInfoList []con.ConnectorTaskID) []*dataplanev1alpha1.TaskInfo {
tasks := make([]*dataplanev1alpha1.TaskInfo, len(taskInfoList))
for i, task := range taskInfoList {
tasks[i] = m.taskToProto(connectorName, task.Task)
}
return tasks
}

func (mapper) taskToProto(name string, taskID int) *dataplanev1alpha1.TaskInfo {
return &dataplanev1alpha1.TaskInfo{
Connector: name,
Task: int32(taskID),
}
}

func (mapper) taskInfoListToProtoStatus(taskInfoList []kafkaconnect.ClusterConnectorTaskInfo) []*dataplanev1alpha1.TaskStatus {
tasks := make([]*dataplanev1alpha1.TaskStatus, len(taskInfoList))
for i, task := range taskInfoList {
tasks[i] = &dataplanev1alpha1.TaskStatus{
Id: int32(task.TaskID),
State: task.State,
WorkerId: task.WorkerID,
Trace: task.Trace,
}
}
return tasks
}

func (mapper) holisticStateToProto(state string) dataplanev1alpha1.HolisticState {
switch state {
case kafkaconnect.ConnectorStatusPaused:
return dataplanev1alpha1.HolisticState_HOLISTIC_STATE_PAUSED
case kafkaconnect.ConnectorStatusStopped:
return dataplanev1alpha1.HolisticState_HOLISTIC_STATE_STOPPED
case kafkaconnect.ConnectorStatusRestarting:
return dataplanev1alpha1.HolisticState_HOLISTIC_STATE_RESTARTING
case kafkaconnect.ConnectorStatusDestroyed:
return dataplanev1alpha1.HolisticState_HOLISTIC_STATE_DESTROYED
case kafkaconnect.ConnectorStatusUnassigned:
return dataplanev1alpha1.HolisticState_HOLISTIC_STATE_UNASSIGNED
case kafkaconnect.ConnectorStatusHealthy:
return dataplanev1alpha1.HolisticState_HOLISTIC_STATE_HEALTHY
case kafkaconnect.ConnectorStatusUnhealthy:
return dataplanev1alpha1.HolisticState_HOLISTIC_STATE_UNHEALTHY
case kafkaconnect.ConnectorStatusDegraded:
return dataplanev1alpha1.HolisticState_HOLISTIC_STATE_DEGRADED
default:
return dataplanev1alpha1.HolisticState_HOLISTIC_STATE_UNKNOWN
}
}

func (m mapper) connectorErrorsToProto(errorInfoList []kafkaconnect.ClusterConnectorInfoError) ([]*dataplanev1alpha1.ConnectorError, error) {
connectErrors := make([]*dataplanev1alpha1.ConnectorError, len(errorInfoList))
for i, errorInfoItem := range errorInfoList {
errorType, err := m.connectorErrorTypeToProto(errorInfoItem.Type)
if err != nil {
return nil, err
}
connectErrors[i] = &dataplanev1alpha1.ConnectorError{
Title: errorInfoItem.Title,
Content: errorInfoItem.Content,
Type: errorType,
}
}
return connectErrors, nil
}

func (mapper) connectorErrorTypeToProto(errorType string) (dataplanev1alpha1.ConnectorError_Type, error) {
switch errorType {
case "ERROR":
return dataplanev1alpha1.ConnectorError_TYPE_ERROR, nil
case "WARNING":
return dataplanev1alpha1.ConnectorError_TYPE_WARNING, nil
default:
return dataplanev1alpha1.ConnectorError_TYPE_UNSPECIFIED, fmt.Errorf("failed to map given error type %q to proto", errorType)
}
}

func (mapper) createConnectorProtoToClientRequest(createConnector *dataplanev1alpha1.CreateConnectorRequest) (*con.CreateConnectorRequest, error) {
if createConnector == nil || createConnector.Connector == nil {
return nil, fmt.Errorf("create connector request is nil")
}

if len(createConnector.Connector.Config) == 0 {
return nil, fmt.Errorf("create connector request config is empty")
}

return &con.CreateConnectorRequest{
Name: createConnector.Connector.Name,
Config: convertStringMapToInterfaceMap(createConnector.Connector.Config),
}, nil
}

// convertStringMapToInterfaceMap converts interface map to string map
func convertStringMapToInterfaceMap(stringMap map[string]string) map[string]interface{} {
interfaceMap := make(map[string]interface{}, len(stringMap))
for key, value := range stringMap {
interfaceMap[key] = value
}
return interfaceMap
}
Loading

0 comments on commit abbd8a2

Please sign in to comment.