1
0
Fork 0
mirror of https://github.com/postmannen/ctrl.git synced 2024-12-14 12:37:31 +00:00
ctrl/requests.go

540 lines
18 KiB
Go
Raw Normal View History

// The structure of how to add new method types to the system.
// -----------------------------------------------------------
// All methods need 3 things:
// - A type definition
// - The type needs a getKind method
// - The type needs a handler method
// Overall structure example shown below.
//
// ---
// type methodCommandCLICommandRequest struct {
// commandOrEvent CommandOrEvent
// }
//
// func (m methodCommandCLICommandRequest) getKind() CommandOrEvent {
// return m.commandOrEvent
// }
//
// func (m methodCommandCLICommandRequest) handler(s *server, message Message, node string) ([]byte, error) {
// ...
// ...
2021-03-11 05:34:36 +00:00
// ackMsg := []byte(fmt.Sprintf("confirmed from node: %v: messageID: %v\n---\n%s---", node, message.ID, out))
// return ackMsg, nil
// }
//
// ---
// You also need to make a constant for the Method, and add
// that constant as the key in the MethodsAvailable map, where
// the value is the actual type you want to map it to with a
// handler method. You also specify if it is a Command or Event,
// and if it is ACK or NACK.
//
// Requests used in sub processes should always start with the
// naming REQSUB. Since the method of a sub process are defined
// within the method handler of the owning reqest type we should
// use the methodREQSUB for these types. The methodREQSUB handler
// does nothing.
//
// Check out the existing code below for more examples.
package steward
import (
2021-03-26 15:04:01 +00:00
"context"
"fmt"
"path/filepath"
"strings"
"time"
)
2021-03-11 16:14:43 +00:00
// Method is used to specify the actual function/method that
// is represented in a typed manner.
type Method string
// ------------------------------------------------------------
// The constants that will be used throughout the system for
// when specifying what kind of Method to send or work with.
const (
2021-08-16 11:01:12 +00:00
// Initial parent method used to start other processes.
REQInitial Method = "REQInitial"
// Get a list of all the running processes.
REQOpProcessList Method = "REQOpProcessList"
// Start up a process.
REQOpProcessStart Method = "REQOpProcessStart"
2021-09-20 09:53:17 +00:00
// Stop up a process.
REQOpProcessStop Method = "REQOpProcessStop"
2021-03-11 16:14:43 +00:00
// Execute a CLI command in for example bash or cmd.
// This is an event type, where a message will be sent to a
// node with the command to execute and an ACK will be replied
// if it was delivered succesfully. The output of the command
// ran will be delivered back to the node where it was initiated
// as a new message.
// The data field is a slice of strings where the first string
// value should be the command, and the following the arguments.
2021-04-04 09:19:17 +00:00
REQCliCommand Method = "REQCliCommand"
// REQCliCommandCont same as normal Cli command, but can be used
2021-06-08 18:52:45 +00:00
// when running a command that will take longer time and you want
// to send the output of the command continually back as it is
// generated, and not wait until the command is finished.
REQCliCommandCont Method = "REQCliCommandCont"
// Send text to be logged to the console.
2021-03-11 16:14:43 +00:00
// The data field is a slice of strings where the first string
// value should be the command, and the following the arguments.
REQToConsole Method = "REQToConsole"
2022-01-12 06:42:41 +00:00
// REQTuiToConsole
REQTuiToConsole Method = "REQTuiToConsole"
// Send text logging to some host by appending the output to a
// file, if the file do not exist we create it.
2021-03-11 16:14:43 +00:00
// A file with the full subject+hostName will be created on
// the receiving end.
// The data field is a slice of strings where the values of the
// slice will be written to the log file.
REQToFileAppend Method = "REQToFileAppend"
2021-04-06 12:05:47 +00:00
// Send text to some host by overwriting the existing content of
// the fileoutput to a file. If the file do not exist we create it.
// A file with the full subject+hostName will be created on
// the receiving end.
// The data field is a slice of strings where the values of the
// slice will be written to the file.
2021-04-13 15:15:13 +00:00
REQToFile Method = "REQToFile"
2022-03-04 14:02:43 +00:00
// REQToFileNACK same as REQToFile but NACK.
REQToFileNACK Method = "REQToFileNACK"
// Initiated by the user.
2022-06-09 03:29:41 +00:00
REQCopySrc Method = "REQCopySrc"
// Initial request for file copying.
// Generated by the source to send initial information to the destination.
2022-06-09 03:29:41 +00:00
REQCopyDst Method = "REQCopyDst"
// Read the source file to be copied to some node.
2022-06-14 05:09:20 +00:00
REQSUBCopySrc Method = "REQSUBCopySrc"
// Write the destination copied to some node.
2022-06-14 05:09:20 +00:00
REQSUBCopyDst Method = "REQSUBCopyDst"
2022-06-09 03:29:41 +00:00
// Send Hello I'm here message.
2021-04-06 03:46:07 +00:00
REQHello Method = "REQHello"
2021-03-11 16:14:43 +00:00
// Error log methods to centralError node.
2021-04-06 05:56:49 +00:00
REQErrorLog Method = "REQErrorLog"
2021-03-11 05:34:36 +00:00
// Echo request will ask the subscriber for a
2021-03-11 16:14:43 +00:00
// reply generated as a new message, and sent back to where
// the initial request was made.
2021-04-06 04:08:26 +00:00
REQPing Method = "REQPing"
// Will generate a reply for a ECHORequest
2021-04-06 04:08:26 +00:00
REQPong Method = "REQPong"
2021-04-06 17:42:03 +00:00
// Http Get
REQHttpGet Method = "REQHttpGet"
2022-02-11 06:27:51 +00:00
// Http Get Scheduled
// The second element of the MethodArgs slice holds the timer defined in seconds.
REQHttpGetScheduled Method = "REQHttpGetScheduled"
2021-04-13 09:28:52 +00:00
// Tail file
REQTailFile Method = "REQTailFile"
2022-01-05 07:47:06 +00:00
// REQNone is used when there should be no reply.
REQNone Method = "REQNone"
2022-05-22 04:36:02 +00:00
// REQTest is used only for testing to be able to grab the output
// of messages.
REQTest Method = "REQTest"
2022-04-07 07:34:06 +00:00
// REQPublicKey will get the public ed25519 key from a node.
2022-02-08 10:49:32 +00:00
REQPublicKey Method = "REQPublicKey"
// REQKeysRequestUpdate will get all the public keys from central if an update is available.
2022-05-24 05:21:48 +00:00
REQKeysRequestUpdate Method = "REQKeysRequestUpdate"
// REQKeysDeliverUpdate will deliver the public from central to a node.
REQKeysDeliverUpdate Method = "REQKeysDeliverUpdate"
// REQKeysAllow
REQKeysAllow Method = "REQKeysAllow"
2022-06-01 05:29:25 +00:00
// REQKeysDelete
REQKeysDelete Method = "REQKeysDelete"
2022-05-22 04:36:02 +00:00
// REQAclRequestUpdate will get all node acl's from central if an update is available.
REQAclRequestUpdate Method = "REQAclRequestUpdate"
// REQAclDeliverUpdate will deliver the acl from central to a node.
REQAclDeliverUpdate Method = "REQAclDeliverUpdate"
2022-05-18 12:43:35 +00:00
// REQAclAddCommand
REQAclAddCommand = "REQAclAddCommand"
// REQAclDeleteCommand
REQAclDeleteCommand = "REQAclDeleteCommand"
2022-05-19 19:35:14 +00:00
// REQAclDeleteSource
REQAclDeleteSource = "REQAclDeleteSource"
// REQGroupNodesAddNode
REQAclGroupNodesAddNode = "REQAclGroupNodesAddNode"
// REQAclGroupNodesDeleteNode
REQAclGroupNodesDeleteNode = "REQAclGroupNodesDeleteNode"
2022-05-20 03:18:26 +00:00
// REQAclGroupNodesDeleteGroup
REQAclGroupNodesDeleteGroup = "REQAclGroupNodesDeleteGroup"
// REQAclGroupCommandsAddCommand
REQAclGroupCommandsAddCommand = "REQAclGroupCommandsAddCommand"
// REQAclGroupCommandsDeleteCommand
REQAclGroupCommandsDeleteCommand = "REQAclGroupCommandsDeleteCommand"
// REQAclGroupCommandsDeleteGroup
REQAclGroupCommandsDeleteGroup = "REQAclGroupCommandsDeleteGroup"
2022-05-21 05:09:35 +00:00
// REQAclExport
REQAclExport = "REQAclExport"
2022-05-21 05:26:36 +00:00
// REQAclImport
REQAclImport = "REQAclImport"
)
// The mapping of all the method constants specified, what type
// it references, and the kind if it is an Event or Command, and
// if it is ACK or NACK.
// Allowed values for the Event field are:
// - EventACK
// - EventNack
//
// The primary use of this table is that messages are not able to
// pass the actual type of the request since it is sent as a string,
// so we use the below table to find the actual type based on that
// string type.
func (m Method) GetMethodsAvailable() MethodsAvailable {
2021-04-03 05:14:39 +00:00
ma := MethodsAvailable{
2021-06-29 06:21:42 +00:00
Methodhandlers: map[Method]methodHandler{
REQInitial: methodREQInitial{
event: EventACK,
},
REQOpProcessList: methodREQOpProcessList{
event: EventACK,
},
REQOpProcessStart: methodREQOpProcessStart{
event: EventACK,
},
2021-09-20 09:53:17 +00:00
REQOpProcessStop: methodREQOpProcessStop{
event: EventACK,
2021-09-20 09:53:17 +00:00
},
2021-04-04 09:19:17 +00:00
REQCliCommand: methodREQCliCommand{
event: EventACK,
},
REQCliCommandCont: methodREQCliCommandCont{
event: EventACK,
2021-06-08 18:52:45 +00:00
},
REQToConsole: methodREQToConsole{
event: EventACK,
},
2022-01-12 06:42:41 +00:00
REQTuiToConsole: methodREQTuiToConsole{
event: EventACK,
2022-01-12 06:42:41 +00:00
},
REQToFileAppend: methodREQToFileAppend{
event: EventACK,
},
2021-04-13 15:15:13 +00:00
REQToFile: methodREQToFile{
event: EventACK,
2021-04-06 12:05:47 +00:00
},
2022-03-04 14:02:43 +00:00
REQToFileNACK: methodREQToFile{
event: EventNACK,
},
2022-06-09 03:29:41 +00:00
REQCopySrc: methodREQCopySrc{
event: EventACK,
},
REQCopyDst: methodREQCopyDst{
event: EventACK,
},
2022-06-14 05:09:20 +00:00
REQSUBCopySrc: methodREQSUB{
event: EventACK,
},
2022-06-14 05:09:20 +00:00
REQSUBCopyDst: methodREQSUB{
event: EventACK,
},
2021-04-06 03:46:07 +00:00
REQHello: methodREQHello{
event: EventNACK,
},
2021-04-06 05:56:49 +00:00
REQErrorLog: methodREQErrorLog{
event: EventACK,
},
2021-04-06 04:08:26 +00:00
REQPing: methodREQPing{
event: EventACK,
2021-03-11 05:34:36 +00:00
},
2021-04-06 04:08:26 +00:00
REQPong: methodREQPong{
event: EventACK,
2021-03-11 05:34:36 +00:00
},
2021-04-06 17:42:03 +00:00
REQHttpGet: methodREQHttpGet{
event: EventACK,
2021-04-06 17:42:03 +00:00
},
2022-02-11 06:27:51 +00:00
REQHttpGetScheduled: methodREQHttpGetScheduled{
event: EventACK,
},
2021-04-13 09:28:52 +00:00
REQTailFile: methodREQTailFile{
event: EventACK,
2021-04-13 09:28:52 +00:00
},
2022-02-08 04:17:38 +00:00
REQPublicKey: methodREQPublicKey{
event: EventACK,
},
2022-05-24 05:21:48 +00:00
REQKeysRequestUpdate: methodREQKeysRequestUpdate{
2022-04-07 07:34:06 +00:00
event: EventNACK,
},
2022-05-24 05:21:48 +00:00
REQKeysDeliverUpdate: methodREQKeysDeliverUpdate{
2022-04-07 07:34:06 +00:00
event: EventNACK,
},
2022-05-24 05:21:48 +00:00
REQKeysAllow: methodREQKeysAllow{
2022-04-20 16:33:52 +00:00
event: EventACK,
},
2022-06-01 05:29:25 +00:00
REQKeysDelete: methodREQKeysDelete{
event: EventACK,
},
REQAclRequestUpdate: methodREQAclRequestUpdate{
event: EventNACK,
},
REQAclDeliverUpdate: methodREQAclDeliverUpdate{
event: EventNACK,
},
2022-05-18 12:43:35 +00:00
REQAclAddCommand: methodREQAclAddCommand{
event: EventACK,
},
REQAclDeleteCommand: methodREQAclDeleteCommand{
2022-05-18 09:26:06 +00:00
event: EventACK,
},
2022-05-19 19:35:14 +00:00
REQAclDeleteSource: methodREQAclDeleteSource{
event: EventACK,
},
REQAclGroupNodesAddNode: methodREQAclGroupNodesAddNode{
event: EventACK,
},
REQAclGroupNodesDeleteNode: methodREQAclGroupNodesDeleteNode{
event: EventACK,
},
2022-05-20 03:18:26 +00:00
REQAclGroupNodesDeleteGroup: methodREQAclGroupNodesDeleteGroup{
event: EventACK,
},
REQAclGroupCommandsAddCommand: methodREQAclGroupCommandsAddCommand{
event: EventACK,
},
REQAclGroupCommandsDeleteCommand: methodREQAclGroupCommandsDeleteCommand{
event: EventACK,
},
REQAclGroupCommandsDeleteGroup: methodREQAclGroupCommandsDeleteGroup{
event: EventACK,
},
2022-05-21 05:09:35 +00:00
REQAclExport: methodREQAclExport{
event: EventACK,
},
2022-05-21 05:26:36 +00:00
REQAclImport: methodREQAclImport{
event: EventACK,
},
2022-05-22 04:36:02 +00:00
REQTest: methodREQTest{
event: EventACK,
},
},
}
return ma
}
2021-07-01 05:42:45 +00:00
// Reply methods. The slice generated here is primarily used within
// the Stew client for knowing what of the req types are generally
// used as reply methods.
2021-06-29 06:21:42 +00:00
func (m Method) GetReplyMethods() []Method {
2022-04-20 04:41:45 +00:00
rm := []Method{REQToConsole, REQTuiToConsole, REQCliCommand, REQCliCommandCont, REQToFile, REQToFileAppend, REQNone}
2021-06-16 19:38:33 +00:00
return rm
}
// getHandler will check the methodsAvailable map, and return the
// method handler for the method given
// as input argument.
func (m Method) getHandler(method Method) methodHandler {
ma := m.GetMethodsAvailable()
mh, _ := ma.CheckIfExists(method)
// mh := ma.Methodhandlers[method]
return mh
}
// getContextForMethodTimeout, will return a context with cancel function
// with the timeout set to the method timeout in the message.
// If the value of timeout is set to -1, we don't want it to stop, so we
// return a context with a timeout set to 200 years.
func getContextForMethodTimeout(ctx context.Context, message Message) (context.Context, context.CancelFunc) {
// If methodTimeout == -1, which means we don't want a timeout, set the
// time out to 200 years.
if message.MethodTimeout == -1 {
return context.WithTimeout(ctx, time.Hour*time.Duration(8760*200))
}
return context.WithTimeout(ctx, time.Second*time.Duration(message.MethodTimeout))
}
// ----
2021-08-16 11:01:12 +00:00
// Initial parent method used to start other processes.
type methodREQInitial struct {
event Event
}
2022-01-27 06:19:04 +00:00
func (m methodREQInitial) getKind() Event {
return m.event
}
func (m methodREQInitial) handler(proc process, message Message, node string) ([]byte, error) {
// proc.procFuncCh <- message
ackMsg := []byte("confirmed from: " + node + ": " + fmt.Sprint(message.ID))
return ackMsg, nil
}
// ----
2021-08-16 11:01:12 +00:00
// place holder method used for sub processes.
// Methods used in sub processes are defined within the the requests
// they are spawned in, so this type is primarily for us to use the
// same logic with sub process requests as we do with normal requests.
2022-06-14 05:09:20 +00:00
type methodREQSUB struct {
event Event
}
2022-06-14 05:09:20 +00:00
func (m methodREQSUB) getKind() Event {
return m.event
}
2022-06-14 05:09:20 +00:00
func (m methodREQSUB) handler(proc process, message Message, node string) ([]byte, error) {
// proc.procFuncCh <- message
ackMsg := []byte("confirmed from: " + node + ": " + fmt.Sprint(message.ID))
return ackMsg, nil
}
// ----
2021-08-16 11:01:12 +00:00
// MethodsAvailable holds a map of all the different method types and the
// associated handler to that method type.
type MethodsAvailable struct {
2021-06-29 06:21:42 +00:00
Methodhandlers map[Method]methodHandler
}
// Check if exists will check if the Method is defined. If true the bool
// value will be set to true, and the methodHandler function for that type
// will be returned.
func (ma MethodsAvailable) CheckIfExists(m Method) (methodHandler, bool) {
// First check if it is a sub process.
2022-06-14 05:09:20 +00:00
if strings.HasPrefix(string(m), "REQSUB") {
// Strip of the uuid after the method name.
sp := strings.Split(string(m), ".")
m = Method(sp[0])
}
2021-06-29 06:21:42 +00:00
mFunc, ok := ma.Methodhandlers[m]
if ok {
return mFunc, true
} else {
return nil, false
}
}
// newReplyMessage will create and send a reply message back to where
// the original provided message came from. The primary use of this
// function is to report back to a node who sent a message with the
// result of the request method of the original message.
//
// The method to use for the reply message when reporting back should
// be specified within a message in the replyMethod field. We will
2021-08-16 11:01:12 +00:00
// pick up that value here, and use it as the method for the new
// request message. If no replyMethod is set we default to the
// REQToFileAppend method type.
//
// There will also be a copy of the original message put in the
// previousMessage field. For the copy of the original message the data
// field will be set to nil before the whole message is put in the
// previousMessage field so we don't copy around the original data in
// the reply response when it is not needed anymore.
2021-08-16 11:01:12 +00:00
func newReplyMessage(proc process, message Message, outData []byte) {
2022-01-05 07:47:06 +00:00
// If REQNone is specified, we don't want to send a reply message
// so we silently just return without sending anything.
if message.ReplyMethod == "REQNone" {
return
}
2021-08-16 11:01:12 +00:00
// If no replyMethod is set we default to writing to writing to
// a log file.
if message.ReplyMethod == "" {
message.ReplyMethod = REQToFileAppend
}
// Make a copy of the message as it is right now to use
// in the previous message field, but set the data field
// to nil so we don't copy around the original data when
// we don't need to for the reply message.
thisMsg := message
thisMsg.Data = nil
2021-08-16 11:01:12 +00:00
// Create a new message for the reply, and put it on the
// ringbuffer to be published.
2022-04-07 07:34:06 +00:00
// TODO: Check that we still got all the fields present that are needed here.
2021-08-16 11:01:12 +00:00
newMsg := Message{
2022-06-22 12:32:58 +00:00
ToNode: message.FromNode,
// The ToNodes field is not needed since it only a concept that exists when messages
// are injected f.ex. on a socket, and there they are directly converted into separate
// node messages. With other words a message in the system are only for single nodes,
// so we don't have to worry about the ToNodes field when creating reply messages.
FromNode: message.ToNode,
Data: outData,
2021-09-16 10:37:46 +00:00
Method: message.ReplyMethod,
MethodArgs: message.ReplyMethodArgs,
MethodTimeout: message.ReplyMethodTimeout,
2021-09-22 14:08:55 +00:00
IsReply: true,
RetryWait: message.RetryWait,
2021-09-16 10:37:46 +00:00
ACKTimeout: message.ReplyACKTimeout,
Retries: message.ReplyRetries,
Directory: message.Directory,
FileName: message.FileName,
2021-08-16 11:01:12 +00:00
// Put in a copy of the initial request message, so we can use it's properties if
// needed to for example create the file structure naming on the subscriber.
PreviousMessage: &thisMsg,
2021-08-16 11:01:12 +00:00
}
2021-08-25 06:56:44 +00:00
sam, err := newSubjectAndMessage(newMsg)
2021-08-16 11:01:12 +00:00
if err != nil {
// In theory the system should drop the message before it reaches here.
2021-09-23 06:19:53 +00:00
er := fmt.Errorf("error: newSubjectAndMessage : %v, message: %v", err, message)
2022-04-01 06:43:14 +00:00
proc.errorKernel.errSend(proc, message, er)
2021-08-16 11:01:12 +00:00
}
2022-01-03 09:40:27 +00:00
2021-08-25 06:56:44 +00:00
proc.toRingbufferCh <- []subjectAndMessage{sam}
2021-08-16 11:01:12 +00:00
}
// selectFileNaming will figure out the correct naming of the file
// structure to use for the reply data.
// It will return the filename, and the tree structure for the folders
// to create.
func selectFileNaming(message Message, proc process) (string, string) {
var fileName string
// As default we set the folder tree to what is specified in the
// message.Directory field. If we don't want that in the checks
// done later we then replace the value with what we want.
folderTree := message.Directory
checkPrefix := func(s string) bool {
if strings.HasPrefix(s, "./") || strings.HasPrefix(s, "/") {
return true
}
return false
}
switch {
case message.PreviousMessage == nil:
// If this was a direct request there are no previous message to take
// information from, so we use the one that are in the current mesage.
fileName = message.FileName
if !checkPrefix(message.Directory) {
folderTree = filepath.Join(proc.configuration.SubscribersDataFolder, message.Directory, string(message.ToNode))
}
case message.PreviousMessage.ToNode != "":
fileName = message.PreviousMessage.FileName
if !checkPrefix(message.PreviousMessage.Directory) {
folderTree = filepath.Join(proc.configuration.SubscribersDataFolder, message.PreviousMessage.Directory, string(message.PreviousMessage.ToNode))
}
case message.PreviousMessage.ToNode == "":
fileName = message.PreviousMessage.FileName
if !checkPrefix(message.PreviousMessage.Directory) {
folderTree = filepath.Join(proc.configuration.SubscribersDataFolder, message.PreviousMessage.Directory, string(message.FromNode))
}
}
return fileName, folderTree
}
// ------------------------------------------------------------
// Subscriber method handlers
// ------------------------------------------------------------
2021-08-16 11:01:12 +00:00
// The methodHandler interface.
type methodHandler interface {
2021-03-08 13:09:14 +00:00
handler(proc process, message Message, node string) ([]byte, error)
2022-01-27 06:19:04 +00:00
getKind() Event
}