Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Architecture design proposal, Basic working of Chat_Service & Executor service usecase for Kubebot #5

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added design/Microservices.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added design/Workflow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 41 additions & 0 deletions kube_Chat/api/handlers/ExecutorClient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package handlers

import (
"context"
"errors"
kubebot "github.com/leopardslab/kubebot/kube_Chat/api/proto"
"github.com/leopardslab/kubebot/kube_Chat/entity"
"google.golang.org/grpc"
"log"
)

type ExecutorClient struct {
conn *grpc.ClientConn
}

func NewExecutorClient(conn *grpc.ClientConn) *ExecutorClient {
return &ExecutorClient{
conn: conn,
}
}

func (e *ExecutorClient) RunCommand(msg *entity.Message) (*entity.CommandResult, error) {
if e.conn == nil {
return nil, errors.New("Grpc conn is nil")
}
reqBody := &kubebot.ExecuteRequest{
CommandStr: msg.Content,
}

c := kubebot.NewExecutorRoutesClient(e.conn)

resp, err := c.ExecuteCommand(context.Background(), reqBody)
if err != nil {
log.Printf("Error whhile calling Executor service. Error : %v", err)
return nil, err
}

cmdResult := entity.NewCommandResult(msg.Content, resp.Result, resp.Error)

return cmdResult, nil
}
59 changes: 59 additions & 0 deletions kube_Chat/api/handlers/MessageServer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package handlers

import (
"context"
"errors"
kubebot "github.com/leopardslab/kubebot/kube_Chat/api/proto"
"github.com/leopardslab/kubebot/kube_Chat/entity"
"github.com/leopardslab/kubebot/kube_Chat/usecase/communication"
"google.golang.org/grpc"
"strings"
)

type MessageServer struct {
kubebot.UnsafeMessageChatServerServer
}

// GetMessageResponse This function is called by Bot to get response for the users message.
func (m *MessageServer) GetMessageResponse(ctx context.Context, req *kubebot.BotCommandRequest) (*kubebot.BotCommandResponse, error) {
var mentions []string
if req == nil {
return nil, errors.New("request is nil")
}
if len(req.Mentions) > 0 {
mentions = strings.Fields(req.Mentions)
}

msg := entity.NewMessage(req.Platform, req.Sender, req.Content, req.BotId, mentions)
serv := communication.NewService(msg)
isCommand, err := serv.CheckMessage()

if err != nil {
return nil, err
}

if isCommand == false {
//TODO Handle dialog flow which will call builder if builder requires some argument this function will return query
}

var conn *grpc.ClientConn
//TODO Add address for GRPC
conn, err = grpc.Dial("address", grpc.WithInsecure())
if err != nil {
return nil, err
}
defer conn.Close()

handler := NewExecutorClient(conn)
result, err := handler.RunCommand(msg)
if err != nil {
return nil, err
}

return &kubebot.BotCommandResponse{
Response: result.Result,
IsQuery: false,
Query: "",
Command: result.Cmd,
}, nil
}
20 changes: 20 additions & 0 deletions kube_Chat/api/proto/Executor.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
syntax = "proto3";
package kubebot_Executor;
option go_package="./kubebot";

message ExecuteRequest{
string commandStr=1;
}

message ExecuteResponse{
string result=1; //formatted result
string error=2;
string details=3;
}


service ExecutorRoutes{

rpc ExecuteCommand(ExecuteRequest) returns(ExecuteResponse){}

}
24 changes: 24 additions & 0 deletions kube_Chat/api/proto/MessageService.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
syntax = "proto3";
package kubebot_Executor;
option go_package="./kubebot";

message BotCommandRequest {
string Content =1;
string Sender=2;
string Mentions=3;
string BotId=4;
string Platform=5;
}

message BotCommandResponse{
string Response=1;
bool isQuery=2; //
string query=3; // If some flags are missing
string command=4; // Original command for which we need additional info
}



service MessageChatServer{
rpc GetMessageResponse(BotCommandRequest) returns (BotCommandResponse){}
}
8 changes: 8 additions & 0 deletions kube_Chat/buf.gen.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: v1
plugins:
- name: go
out: ./
opt: paths=source_relative
- name: go-grpc
out: ./
opt: paths=source_relative
10 changes: 10 additions & 0 deletions kube_Chat/buf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: v1
deps:
- buf.build/grpc-ecosystem/grpc-gateway

breaking:
use:
- FILE
lint:
use:
- DEFAULT
Empty file added kube_Chat/build/Dockerfile
Empty file.
49 changes: 49 additions & 0 deletions kube_Chat/entity/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package entity

type Command struct {
CmdStr string
Head string
Arguments []string
Flags []string
ArgCount int
Category string
}

type CommandResult struct {
Cmd string
Result string
Error string // error from the exec
}

type CommandContext struct {
Query []string //Required arguments for particular command
Command Command
IsRequired bool // False if all arguments are satisfied
}

func NewCommand(cmd, head, category string, args, flags []string, argc int) (*Command, error) {
return &Command{
CmdStr: cmd,
Head: head,
Arguments: args,
Flags: flags,
ArgCount: argc,
Category: category,
}, nil
}

func NewCommandResult(cmd, result, error string) *CommandResult {
return &CommandResult{
Cmd: cmd,
Result: result,
Error: error,
}
}

func NewCommandContext(com Command, query []string, req bool) *CommandContext {
return &CommandContext{
Command: com,
Query: query,
IsRequired: req,
}
}
19 changes: 19 additions & 0 deletions kube_Chat/entity/message.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package entity

type Message struct {
Platform string
Sender string
Content string
BotId string
Mentions []string
}

func NewMessage(platform, sender, content, botId string, mentions []string) *Message {
return &Message{
Platform: platform,
Sender: sender,
Content: content,
BotId: botId,
Mentions: mentions,
}
}
7 changes: 7 additions & 0 deletions kube_Chat/usecase/builder/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package builder

import "github.com/leopardslab/kubebot/kube_Chat/entity"

type Usecase interface {
BuildCommand() (*entity.Command, error)
}
18 changes: 18 additions & 0 deletions kube_Chat/usecase/builder/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package builder

import (
"github.com/leopardslab/kubebot/kube_Chat/entity"
)

type Service struct {
cmdCtx *entity.CommandContext
}

func (s *Service) BuildCommand() (*entity.CommandContext, error) {
//This function checks if the command detected by dialog flow req some argument or not if req then sends response as
//query to user and if its complete then to the executor

//TODO Add logic for building commands

return s.cmdCtx, nil
}
6 changes: 6 additions & 0 deletions kube_Chat/usecase/communication/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package communication

type Usecase interface {
CheckMessage() (bool, error)
ForwardMessage(serviceId string) error
}
36 changes: 36 additions & 0 deletions kube_Chat/usecase/communication/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package communication

import (
"errors"
"github.com/leopardslab/kubebot/kube_Chat/entity"
"strings"
)

type Service struct {
Msg *entity.Message
}

func NewService(msg *entity.Message) *Service {
return &Service{
Msg: msg,
}

}

func (s *Service) CheckMessage() (bool, error) {
// Msg.content should be the msg after bot's mentionId
// @kubeBot run kubectl get pods. :: msg.content -> run kubectl get pods
if s.Msg == nil || len(s.Msg.Content) <= 0 {
words := strings.Fields(s.Msg.Content)
if words[0] != "run" {
return false, nil
}
return true, nil
}
return false, errors.New("Error : Message cannot nil")
}

func (s *Service) ForwardMessage(serviceId string) error {
//TODO
return nil
}
7 changes: 7 additions & 0 deletions kube_Chat/usecase/dialogflow/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dialogflow

import "github.com/leopardslab/kubebot/kube_Chat/entity"

type Usecase interface {
DetectCommand() (*entity.CommandContext, error)
}
52 changes: 52 additions & 0 deletions kube_Chat/usecase/dialogflow/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package dialogflow

import (
dg "cloud.google.com/go/dialogflow/apiv2"
"context"
"fmt"
"github.com/leopardslab/kubebot/kube_Chat/entity"
"google.golang.org/api/option"
"google.golang.org/genproto/googleapis/cloud/dialogflow/v2"
"log"
)

type CommandDetect struct {
Message *entity.Message
Client *dg.SessionsClient
ProjectID string
}

func NewFLowAgent(credentialJSON, projectID string, msg *entity.Message) (*CommandDetect, error) {
client, err := dg.NewSessionsClient(context.Background(), option.WithCredentialsFile(credentialJSON))
if err != nil {
return nil, err
}
return &CommandDetect{
Message: msg,
Client: client,
ProjectID: projectID,
}, nil
}

func (c *CommandDetect) DetectCommand() (*entity.CommandContext, error) {

resp, err := c.Client.DetectIntent(context.Background(), &dialogflow.DetectIntentRequest{
Session: fmt.Sprintf("projects/%s/agent/sessions/%s", c.ProjectID, c.Message.Sender),
QueryInput: &dialogflow.QueryInput{
Input: &dialogflow.QueryInput_Text{Text: &dialogflow.TextInput{
Text: c.Message.Content,
LanguageCode: "en",
}},
},
})
if err != nil {
return nil, err
}
result := resp.GetQueryResult().FulfillmentText

log.Printf("DialogFlow Response : %s", result)

//TODO add additional logic to call Command builder and check for requirements of the command

return nil, nil // Change the return.
}
Loading