github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/providers/agent/mcorpc/mcorpc.go (about) 1 // Copyright (c) 2020-2022, R.I. Pienaar and the Choria Project contributors 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 // Package mcorpc provides a compatibility layer between Choria and 6 // legacy MCollective SimpleRPC Agents 7 // 8 // Agents can be written in the Go language, compiled into the binaries 9 // and be interacted with from the ruby MCollective client. 10 // 11 // It's planned to provide a backward compatible interface so that old 12 // ruby agents, authorization and auditing will be usable inside the 13 // Choria daemon via a shell-out mechanism 14 package mcorpc 15 16 import ( 17 "encoding/json" 18 "fmt" 19 "time" 20 21 "github.com/choria-io/go-choria/config" 22 "github.com/choria-io/go-choria/protocol" 23 "github.com/choria-io/go-choria/srvcache" 24 "github.com/choria-io/go-choria/validator" 25 ) 26 27 // ChoriaFramework provides access to the choria framework 28 type ChoriaFramework interface { 29 Configuration() *config.Config 30 FacterDomain() (string, error) 31 FacterCmd() string 32 MiddlewareServers() (srvcache.Servers, error) 33 NewTransportFromJSON(data []byte) (protocol.TransportMessage, error) 34 ProvisionMode() bool 35 UniqueID() string 36 Certname() string 37 } 38 39 // StatusCode is a reply status as defined by MCollective SimpleRPC - integers 0 to 5 40 // 41 // See the constants OK, RPCAborted, UnknownRPCAction, MissingRPCData, InvalidRPCData and UnknownRPCError 42 type StatusCode uint8 43 44 const ( 45 // OK is the reply status when all worked 46 OK = StatusCode(iota) 47 48 // Aborted is status for when the action could not run, most failures in an action should set this 49 Aborted 50 51 // UnknownAction is the status for unknown actions requested 52 UnknownAction 53 54 // MissingData is the status for missing input data 55 MissingData 56 57 // InvalidData is the status for invalid input data 58 InvalidData 59 60 // UnknownError is the status general failures in agents should set when things go bad 61 UnknownError 62 ) 63 64 // Reply is the reply data as stipulated by MCollective RPC system. The Data 65 // has to be something that can be turned into JSON using the normal Marshal system 66 type Reply struct { 67 Action string `json:"action"` 68 Statuscode StatusCode `json:"statuscode"` 69 Statusmsg string `json:"statusmsg"` 70 Data any `json:"data"` 71 DisableResponse bool `json:"-"` 72 } 73 74 // Request is a request as defined by the MCollective RPC system. 75 // The input data is stored in Data as JSON text unprocessed, the 76 // system at this level has no idea what is in there. In your Agent 77 // you can choose to use the ParseRequestData function to translate 78 // this for you or just do whatever JSON parsing you like 79 type Request struct { 80 Agent string `json:"agent"` 81 Action string `json:"action"` 82 Data json.RawMessage `json:"data"` 83 RequestID string `json:"requestid"` 84 SenderID string `json:"senderid"` 85 CallerID string `json:"callerid"` 86 Collective string `json:"collective"` 87 TTL int `json:"ttl"` 88 Time time.Time `json:"time"` 89 Filter *protocol.Filter `json:"-"` 90 CallerPublicData string `json:"-"` 91 SignerPublicData string `json:"-"` 92 } 93 94 // ParseRequestData parses the request parameters received from the client into a target structure 95 // 96 // Validation is supported, the example below does a `shellsafe` check on the data prior to returning 97 // it, should the check fail appropriate errors will be set on the reply data 98 // 99 // Example used in a action: 100 // 101 // var rparams struct { 102 // Package string `json:"package" validate:"shellsafe"` 103 // } 104 // 105 // if !mcorpc.ParseRequestData(&rparams, req, reply) { 106 // // the function already set appropriate errors on reply 107 // return 108 // } 109 // 110 // // do stuff with rparams.Package 111 func ParseRequestData(target any, request *Request, reply *Reply) bool { 112 err := json.Unmarshal(request.Data, target) 113 if err != nil { 114 reply.Statuscode = InvalidData 115 reply.Statusmsg = fmt.Sprintf("Could not parse request data for %s#%s: %s", request.Agent, request.Action, err) 116 return false 117 } 118 119 ok, err := validator.ValidateStruct(target) 120 if !ok { 121 reply.Statuscode = InvalidData 122 reply.Statusmsg = fmt.Sprintf("Validation failed: %s", err) 123 return false 124 } 125 126 return true 127 }