-
Notifications
You must be signed in to change notification settings - Fork 20
/
Copy pathbuiltins.go
168 lines (149 loc) · 4.78 KB
/
builtins.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package act
import (
sdkAct "github.com/gatewayd-io/gatewayd-plugin-sdk/act"
"github.com/gatewayd-io/gatewayd-plugin-sdk/databases/postgres"
"github.com/gatewayd-io/gatewayd-plugin-sdk/logging"
gerr "github.com/gatewayd-io/gatewayd/errors"
"github.com/jackc/pgx/v5/pgproto3"
"github.com/rs/zerolog"
"github.com/spf13/cast"
)
const (
// TerminateDefaultParamCount is the default parameter count for the terminate action.
TerminateDefaultParamCount = 2
// LogDefaultKeyCount is the default key count in the metadata for the log action.
LogDefaultKeyCount = 3
// These are the keys used to pass the logger and the result to the built-in actions.
LoggerKey = "__logger__"
ResultKey = "__result__"
)
// BuiltinSignals returns a map of built-in signals.
func BuiltinSignals() map[string]*sdkAct.Signal {
return map[string]*sdkAct.Signal{
"passthrough": sdkAct.Passthrough(),
"terminate": sdkAct.Terminate(),
"log": {Name: "log"},
}
}
// BuiltinPolicies returns a map of built-in policies.
func BuiltinPolicies() map[string]*sdkAct.Policy {
return map[string]*sdkAct.Policy{
"passthrough": sdkAct.MustNewPolicy("passthrough", "true", nil),
"terminate": sdkAct.MustNewPolicy(
"terminate",
`Signal.terminate == true && Policy.terminate == "stop"`,
map[string]any{"terminate": "stop"},
),
"log": sdkAct.MustNewPolicy(
"log",
`Signal.log == true && Policy.log == "enabled"`,
map[string]any{"log": "enabled"},
),
}
}
// BuiltinActions returns a map of built-in actions.
func BuiltinActions() map[string]*sdkAct.Action {
return map[string]*sdkAct.Action{
"passthrough": {
Name: "passthrough",
Metadata: nil,
Sync: true,
Terminal: false,
Run: Passthrough,
},
"terminate": {
Name: "terminate",
Metadata: nil,
Sync: true,
Terminal: true,
Run: Terminate,
},
"log": {
Name: "log",
Metadata: nil,
Sync: false,
Terminal: false,
Run: Log,
},
}
}
// Passthrough is a built-in action that always returns true and no error.
func Passthrough(map[string]any, ...sdkAct.Parameter) (any, error) {
return true, nil
}
// Terminate is a built-in action that terminates the connection if the
// terminate signal is true and the policy is set to "stop". The action
// can optionally receive a result parameter.
func Terminate(_ map[string]any, params ...sdkAct.Parameter) (any, error) {
if len(params) == 0 || params[0].Key != LoggerKey {
// No logger parameter or the first parameter is not a logger.
return nil, gerr.ErrLoggerRequired
}
logger, isValid := params[0].Value.(zerolog.Logger)
if !isValid {
// The first parameter is not a logger.
return nil, gerr.ErrLoggerRequired
}
if len(params) < TerminateDefaultParamCount || params[1].Key != ResultKey {
logger.Debug().Msg(
"terminate action can optionally receive a result parameter")
return true, nil
}
result, isValid := params[1].Value.(map[string]any)
if !isValid {
logger.Debug().Msg("terminate action received a non-map result parameter")
return true, nil
}
// If the result from the plugin does not contain a response,
// yet it is a terminal action (hence running this action),
// add an error response to the result and terminate the connection.
if _, exists := result["response"]; !exists {
logger.Trace().Fields(result).Msg(
"Terminating without response, returning an error response")
response, err := (&pgproto3.Terminate{}).Encode(
postgres.ErrorResponse(
"Request terminated",
"ERROR",
"42000",
"Policy terminated the request",
),
)
if err != nil {
// This should never happen, since everything is hardcoded.
logger.Error().Err(err).Msg("Failed to encode the error response")
return nil, gerr.ErrMsgEncodeError.Wrap(err)
}
result["response"] = response
}
return result, nil
}
// Log is a built-in action that logs the data received from the plugin.
func Log(data map[string]any, params ...sdkAct.Parameter) (any, error) {
if len(params) == 0 || params[0].Key != LoggerKey {
// No logger parameter or the first parameter is not a logger.
return nil, gerr.ErrLoggerRequired
}
logger, ok := params[0].Value.(zerolog.Logger)
if !ok {
// The first parameter is not a logger.
return nil, gerr.ErrLoggerRequired
}
fields := map[string]any{}
if len(data) > LogDefaultKeyCount {
for key, value := range data {
// Skip these necessary fields, as they are already used by the logger.
// level: The log level.
// message: The log message.
// log: The log signal.
if key == "level" || key == "message" || key == "log" {
continue
}
// Add the rest of the fields to the logger as extra fields.
fields[key] = value
}
}
logger.WithLevel(
logging.GetZeroLogLevel(cast.ToString(data["level"])),
).Fields(fields).Msg(cast.ToString(data["message"]))
return true, nil
}