diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index ff0c833d..928bd34f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -19,10 +19,10 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Set up Go 1.21 - uses: actions/setup-go@v3 + - name: Set up Go 1.22 + uses: actions/setup-go@v5 with: - go-version: "1.21" + go-version: "1.22" cache: true - name: Install nfpm for building Linux packages run: go install github.com/goreleaser/nfpm/v2/cmd/nfpm@latest diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index aff74d7c..b86ca630 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -43,19 +43,21 @@ jobs: --health-retries 5 steps: - name: Checkout 🛎️ - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install Go 🧑‍💻 - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: - go-version: "1.21" + go-version: "1.22" - name: Lint code issues 🚨 - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v4 with: + version: "v1.56" skip-pkg-cache: true + install-mode: "goinstall" - name: Run Go tests 🔬 run: go test -p 1 -cover -covermode atomic -coverprofile=profile.cov -v ./... @@ -89,17 +91,17 @@ jobs: --health-retries 5 steps: - name: Checkout 🛎️ - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install Go 🧑‍💻 - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: - go-version: "1.21" + go-version: "1.22" - name: Checkout test plugin 🛎️ - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: gatewayd-io/plugin-template-go path: plugin-template-go @@ -113,7 +115,6 @@ jobs: export SHA256SUM=$(sha256sum ptg | awk '{print $1}') cat < gatewayd_plugins.yaml compatibilityPolicy: "strict" - terminationPolicy: "stop" metricsMergerPeriod: 1s healthCheckPeriod: 1s reloadOnCrash: true diff --git a/.gitignore b/.gitignore index 78b3074d..2a04f75e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out +go-carpet-coverage-out* # Dependency directories (remove the comment below to include it) # vendor/ diff --git a/.golangci.yaml b/.golangci.yaml index 1437f382..ecb52445 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -68,6 +68,8 @@ linters-settings: - "gopkg.in/yaml.v3" - "github.com/zenizh/go-capturer" - "gopkg.in/natefinch/lumberjack.v2" + - "github.com/expr-lang/expr" + - "github.com/jackc/pgx/v5/pgproto3" test: files: - $test @@ -84,6 +86,7 @@ linters-settings: - "github.com/panjf2000/gnet/v2" - "github.com/spf13/cobra" - "github.com/knadh/koanf" + - "github.com/spf13/cast" tagalign: align: false sort: false diff --git a/Dockerfile b/Dockerfile index bdd0386e..d01054db 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 # Use the official golang image to build the binary. -FROM golang:1.21-alpine3.18 as builder +FROM golang:1.22-alpine3.18 as builder ARG TARGETOS ARG TARGETARCH diff --git a/Makefile b/Makefile index 1e1fb437..bdcf9929 100644 --- a/Makefile +++ b/Makefile @@ -108,7 +108,7 @@ clean: @rm -rf dist test: - @go test -v ./... + @go test -v -cover -coverprofile=c.out ./... test-race: @go test -race -v ./... diff --git a/act/builtins.go b/act/builtins.go new file mode 100644 index 00000000..f9cf474d --- /dev/null +++ b/act/builtins.go @@ -0,0 +1,162 @@ +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") + result["response"] = (&pgproto3.Terminate{}).Encode( + postgres.ErrorResponse( + "Request terminated", + "ERROR", + "42000", + "Policy terminated the request", + ), + ) + } + + 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 +} diff --git a/act/registry.go b/act/registry.go new file mode 100644 index 00000000..6dea7a59 --- /dev/null +++ b/act/registry.go @@ -0,0 +1,285 @@ +package act + +import ( + "context" + "errors" + "slices" + "time" + + sdkAct "github.com/gatewayd-io/gatewayd-plugin-sdk/act" + "github.com/gatewayd-io/gatewayd/config" + gerr "github.com/gatewayd-io/gatewayd/errors" + "github.com/rs/zerolog" +) + +type IRegistry interface { + Add(policy *sdkAct.Policy) + Apply(signals []sdkAct.Signal) []*sdkAct.Output + Run(output *sdkAct.Output, params ...sdkAct.Parameter) (any, *gerr.GatewayDError) +} + +// Registry keeps track of all policies and actions. +type Registry struct { + logger zerolog.Logger + // Timeout for policy evaluation. + policyTimeout time.Duration + + Signals map[string]*sdkAct.Signal + Policies map[string]*sdkAct.Policy + Actions map[string]*sdkAct.Action + DefaultPolicy *sdkAct.Policy + DefaultSignal *sdkAct.Signal +} + +var _ IRegistry = (*Registry)(nil) + +// NewActRegistry creates a new act registry with the specified default policy and timeout +// and the builtin signals, policies, and actions. +func NewActRegistry( + builtinSignals map[string]*sdkAct.Signal, + builtinsPolicies map[string]*sdkAct.Policy, + builtinActions map[string]*sdkAct.Action, + defaultPolicy string, + policyTimeout time.Duration, + logger zerolog.Logger, +) *Registry { + if builtinSignals == nil || builtinsPolicies == nil || builtinActions == nil { + logger.Warn().Msg("Builtin signals, policies, or actions are nil, not adding") + return nil + } + + for _, signal := range builtinSignals { + if signal == nil { + logger.Warn().Msg("Signal is nil, not adding") + return nil + } + logger.Debug().Str("name", signal.Name).Msg("Registered builtin signal") + } + + for _, policy := range builtinsPolicies { + if policy == nil { + logger.Warn().Msg("Policy is nil, not adding") + return nil + } + logger.Debug().Str("name", policy.Name).Msg("Registered builtin policy") + } + + for _, action := range builtinActions { + if action == nil { + logger.Warn().Msg("Action is nil, not adding") + return nil + } + logger.Debug().Str("name", action.Name).Msg("Registered builtin action") + } + + // The default policy must exist, otherwise use passthrough. + if _, exists := builtinsPolicies[defaultPolicy]; !exists || defaultPolicy == "" { + logger.Warn().Str("name", defaultPolicy).Msgf( + "The specified default policy does not exist, using %s", config.DefaultPolicy) + defaultPolicy = config.DefaultPolicy + } + + logger.Debug().Str("name", defaultPolicy).Msg("Using default policy") + + return &Registry{ + logger: logger, + policyTimeout: policyTimeout, + Signals: builtinSignals, + Policies: builtinsPolicies, + Actions: builtinActions, + DefaultPolicy: builtinsPolicies[defaultPolicy], + DefaultSignal: builtinSignals[defaultPolicy], + } +} + +// Add adds a policy to the registry. +func (r *Registry) Add(policy *sdkAct.Policy) { + if policy == nil { + r.logger.Warn().Msg("Policy is nil, not adding") + return + } + + if _, exists := r.Policies[policy.Name]; exists { + r.logger.Warn().Str("name", policy.Name).Msg("Policy already exists, overwriting") + } + + // Builtin policies are can be overwritten by user-defined policies. + r.Policies[policy.Name] = policy +} + +// Apply applies the signals to the registry and returns the outputs. +func (r *Registry) Apply(signals []sdkAct.Signal) []*sdkAct.Output { + // If there are no signals, apply the default policy. + if len(signals) == 0 { + r.logger.Debug().Msg("No signals provided, applying default signal") + return r.Apply([]sdkAct.Signal{*r.DefaultSignal}) + } + + // Separate terminal and non-terminal signals to find contradictions. + terminal := []string{} + nonTerminal := []string{} + for _, signal := range signals { + action, exists := r.Actions[signal.Name] + if exists && action.Sync && action.Terminal { + terminal = append(terminal, signal.Name) + } else if exists && action.Sync && !action.Terminal { + nonTerminal = append(nonTerminal, signal.Name) + } + } + + outputs := []*sdkAct.Output{} + evalErr := false + for _, signal := range signals { + // Ignore contradictory actions (forward vs. terminate) if one of the signals is terminal. + // If the signal is terminal, all non-terminal signals are ignored. Also, it only + // makes sense to have a terminal signal if the action is synchronous and terminal. + if len(terminal) > 0 && slices.Contains(nonTerminal, signal.Name) { + r.logger.Warn().Str("name", signal.Name).Msg( + "Terminal signal takes precedence, ignoring non-terminal signals") + continue + } + + // Apply the signal and append the output to the list of outputs. + output, err := r.apply(signal) + if err != nil { + r.logger.Error().Err(err).Str("name", signal.Name).Msg("Error applying signal") + // If there is an error evaluating the policy, continue to the next signal. + // This also prevents stack overflows from infinite loops of the external + // if condition below. + if errors.Is(err, gerr.ErrEvalError) { + evalErr = true + } + continue + } + + outputs = append(outputs, output) + } + + if len(outputs) == 0 && !evalErr { + return r.Apply([]sdkAct.Signal{*r.DefaultSignal}) + } + + return outputs +} + +// apply applies the signal to the registry and returns the output. +func (r *Registry) apply(signal sdkAct.Signal) (*sdkAct.Output, *gerr.GatewayDError) { + action, exists := r.Actions[signal.Name] + if !exists { + return nil, gerr.ErrActionNotMatched + } + + policy, exists := r.Policies[action.Name] + if !exists { + return nil, gerr.ErrPolicyNotMatched + } + + // Create a context with a timeout for policy evaluation. + ctx, cancel := context.WithTimeout(context.Background(), r.policyTimeout) + defer cancel() + + // Evaluate the policy. + // TODO: Policy should be able to receive other parameters like server and client IPs, etc. + verdict, err := policy.Eval( + ctx, sdkAct.Input{ + Name: signal.Name, + Policy: policy.Metadata, + Signal: signal.Metadata, + // Action dictates the sync mode, not the signal. + Sync: action.Sync, + }, + ) + if err != nil { + return nil, gerr.ErrEvalError.Wrap(err) + } + + return &sdkAct.Output{ + MatchedPolicy: policy.Name, + Verdict: verdict, + Metadata: signal.Metadata, + Terminal: action.Terminal, + Sync: action.Sync, + }, nil +} + +// Run runs the function associated with the output.MatchedPolicy and +// returns its result. If the action is synchronous, the result is returned +// immediately. If the action is asynchronous, the result is nil and the +// error is ErrAsyncAction, which is a sentinel error to indicate that the +// action is running asynchronously. +func (r *Registry) Run( + output *sdkAct.Output, params ...sdkAct.Parameter, +) (any, *gerr.GatewayDError) { + // In certain cases, the output may be nil, for example, if the policy + // evaluation fails. In this case, the run is aborted. + if output == nil { + // This should never happen, since the output is always set by the registry + // to be the default policy if no signals are provided. + r.logger.Debug().Msg("Output is nil, run aborted") + return nil, gerr.ErrNilPointer + } + + action, ok := r.Actions[output.MatchedPolicy] + if !ok { + r.logger.Warn().Str("matched_policy", output.MatchedPolicy).Msg( + "Action does not exist, run aborted") + return nil, gerr.ErrActionNotExist + } + + // Prepend the logger to the parameters. + params = append([]sdkAct.Parameter{WithLogger(r.logger)}, params...) + + // If the action is synchronous, run it and return the result immediately. + if action.Sync { + r.logger.Debug().Fields(map[string]interface{}{ + "execution_mode": "sync", + "action": action.Name, + }).Msgf("Running action") + + output, err := action.Run(output.Metadata, params...) + if err != nil { + r.logger.Error().Err(err).Str("action", action.Name).Msg("Error running action") + return nil, gerr.ErrRunningAction.Wrap(err) + } + return output, nil + } + + r.logger.Debug().Fields(map[string]interface{}{ + "execution_mode": "async", + "action": action.Name, + }).Msgf("Running action") + + // Run the action asynchronously. + // TODO: Add a way to cancel the action. + go func( + action *sdkAct.Action, + output *sdkAct.Output, + params []sdkAct.Parameter, + logger zerolog.Logger, + ) { + _, err := action.Run(output.Metadata, params...) + if err != nil { + logger.Error().Err(err).Str("action", action.Name).Msg("Error running action") + } + }(action, output, params, r.logger) + + return nil, gerr.ErrAsyncAction +} + +// WithLogger returns a parameter with the logger to be used by the action. +// This is automatically prepended to the parameters when running an action. +func WithLogger(logger zerolog.Logger) sdkAct.Parameter { + return sdkAct.Parameter{ + Key: LoggerKey, + Value: logger, + } +} + +// WithResult returns a parameter with the result of the plugin hook +// to be used by the action. +func WithResult(result map[string]any) sdkAct.Parameter { + return sdkAct.Parameter{ + Key: ResultKey, + Value: result, + } +} diff --git a/act/registry_test.go b/act/registry_test.go new file mode 100644 index 00000000..e3e54893 --- /dev/null +++ b/act/registry_test.go @@ -0,0 +1,466 @@ +package act + +import ( + "bytes" + "testing" + "time" + + sdkAct "github.com/gatewayd-io/gatewayd-plugin-sdk/act" + "github.com/gatewayd-io/gatewayd/config" + gerr "github.com/gatewayd-io/gatewayd/errors" + "github.com/rs/zerolog" + "github.com/spf13/cast" + "github.com/stretchr/testify/assert" +) + +// Test_NewRegistry tests the NewRegistry function. +func Test_NewRegistry(t *testing.T) { + logger := zerolog.Logger{} + + actRegistry := NewActRegistry( + BuiltinSignals(), BuiltinPolicies(), BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + assert.NotNil(t, actRegistry) + assert.NotNil(t, actRegistry.Signals) + assert.NotNil(t, actRegistry.Policies) + assert.NotNil(t, actRegistry.Actions) + assert.Equal(t, config.DefaultPolicy, actRegistry.DefaultPolicy.Name) + assert.Equal(t, config.DefaultPolicy, actRegistry.DefaultSignal.Name) +} + +// Test_NewRegistry_NilSignals tests the NewRegistry function with nil signals, +// actions, and policies. It should return a nil registry. +func Test_NewRegistry_NilBuiltins(t *testing.T) { + buf := bytes.Buffer{} + logger := zerolog.New(&buf) + actRegistry := NewActRegistry( + nil, nil, nil, config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + assert.Nil(t, actRegistry) + assert.Contains(t, buf.String(), "Builtin signals, policies, or actions are nil, not adding") +} + +// Test_NewRegistry_NilPolicy tests the NewRegistry function with a nil signal. +// It should return a nil registry. +func Test_NewRegistry_NilSignal(t *testing.T) { + buf := bytes.Buffer{} + logger := zerolog.New(&buf) + actRegistry := NewActRegistry( + map[string]*sdkAct.Signal{ + "bad": nil, + }, + BuiltinPolicies(), BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + assert.Nil(t, actRegistry) + assert.Contains(t, buf.String(), "Signal is nil, not adding") +} + +// Test_NewRegistry_NilPolicy tests the NewRegistry function with a nil policy. +// It should return a nil registry. +func Test_NewRegistry_NilPolicy(t *testing.T) { + buf := bytes.Buffer{} + logger := zerolog.New(&buf) + actRegistry := NewActRegistry( + BuiltinSignals(), + map[string]*sdkAct.Policy{ + "bad": nil, + }, + BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + assert.Nil(t, actRegistry) + assert.Contains(t, buf.String(), "Policy is nil, not adding") +} + +// Test_NewRegistry_NilAction tests the NewRegistry function with a nil action. +// It should return a nil registry. +func Test_NewRegistry_NilAction(t *testing.T) { + buf := bytes.Buffer{} + logger := zerolog.New(&buf) + actRegistry := NewActRegistry( + BuiltinSignals(), BuiltinPolicies(), + map[string]*sdkAct.Action{ + "bad": nil, + }, + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + assert.Nil(t, actRegistry) + assert.Contains(t, buf.String(), "Action is nil, not adding") +} + +// Test_Add tests the Add function of the act registry. +func Test_Add(t *testing.T) { + actRegistry := NewActRegistry( + BuiltinSignals(), BuiltinPolicies(), BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, zerolog.Logger{}) + assert.NotNil(t, actRegistry) + + assert.Len(t, actRegistry.Policies, len(BuiltinPolicies())) + actRegistry.Add(&sdkAct.Policy{Name: "test-policy", Policy: "true"}) + assert.NotNil(t, actRegistry.Policies["test-policy"]) + assert.Equal(t, "test-policy", actRegistry.Policies["test-policy"].Name) + assert.Equal(t, "true", actRegistry.Policies["test-policy"].Policy) + assert.Nil(t, actRegistry.Policies["test-policy"].Metadata) + assert.Len(t, actRegistry.Policies, len(BuiltinPolicies())+1) +} + +// Test_Add_NilPolicy tests the Add function of the act registry with a nil policy. +func Test_Add_NilPolicy(t *testing.T) { + buf := bytes.Buffer{} + logger := zerolog.New(&buf) + actRegistry := NewActRegistry( + BuiltinSignals(), map[string]*sdkAct.Policy{}, BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + assert.NotNil(t, actRegistry) + + assert.Len(t, actRegistry.Policies, 0) + actRegistry.Add(nil) + assert.Len(t, actRegistry.Policies, 0) + assert.Contains(t, buf.String(), "Policy is nil, not adding") +} + +// Test_Add_ExistentPolicy tests the Add function of the act registry with an existent policy. +func Test_Add_ExistentPolicy(t *testing.T) { + buf := bytes.Buffer{} + logger := zerolog.New(&buf) + actRegistry := NewActRegistry( + BuiltinSignals(), BuiltinPolicies(), BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + assert.NotNil(t, actRegistry) + + assert.Len(t, actRegistry.Policies, len(BuiltinPolicies())) + actRegistry.Add(BuiltinPolicies()["passthrough"]) + assert.Len(t, actRegistry.Policies, len(BuiltinPolicies())) + assert.Contains(t, buf.String(), "Policy already exists, overwriting") +} + +// Test_Apply tests the Apply function of the act registry. +func Test_Apply(t *testing.T) { + actRegistry := NewActRegistry( + BuiltinSignals(), BuiltinPolicies(), BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, zerolog.Logger{}) + assert.NotNil(t, actRegistry) + + outputs := actRegistry.Apply([]sdkAct.Signal{ + *sdkAct.Passthrough(), + }) + assert.NotNil(t, outputs) + assert.Len(t, outputs, 1) + assert.Equal(t, "passthrough", outputs[0].MatchedPolicy) + assert.Nil(t, outputs[0].Metadata) + assert.True(t, outputs[0].Sync) + assert.True(t, cast.ToBool(outputs[0].Verdict)) + assert.False(t, outputs[0].Terminal) +} + +// Test_Apply_NoSignals tests the Apply function of the act registry with no signals. +// It should apply the default policy. +func Test_Apply_NoSignals(t *testing.T) { + buf := bytes.Buffer{} + logger := zerolog.New(&buf) + actRegistry := NewActRegistry( + BuiltinSignals(), BuiltinPolicies(), BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + assert.NotNil(t, actRegistry) + + outputs := actRegistry.Apply([]sdkAct.Signal{}) + assert.NotNil(t, outputs) + assert.Len(t, outputs, 1) + assert.Equal(t, "passthrough", outputs[0].MatchedPolicy) + assert.Nil(t, outputs[0].Metadata) + assert.True(t, outputs[0].Sync) + assert.True(t, cast.ToBool(outputs[0].Verdict)) + assert.False(t, outputs[0].Terminal) + assert.Contains(t, buf.String(), "No signals provided, applying default signal") +} + +// Test_Apply_ContradictorySignals tests the Apply function of the act registry +// with contradictory signals. The terminate signal should take precedence over +// the passthrough signal because it is a terminal action. The passthrough +// signal should be ignored. +func Test_Apply_ContradictorySignals(t *testing.T) { + // The following signals are contradictory because they have different actions. + // The terminate signal should take precedence over the passthrough signal. + // The order of the signals is NOT important. + signals := [][]sdkAct.Signal{ + { + *sdkAct.Terminate(), + *sdkAct.Passthrough(), + *sdkAct.Log("info", "test", map[string]any{"async": true}), + }, + { + *sdkAct.Passthrough(), + *sdkAct.Terminate(), + *sdkAct.Log("info", "test", map[string]any{"async": true}), + }, + } + + buf := bytes.Buffer{} + logger := zerolog.New(&buf) + actRegistry := NewActRegistry( + BuiltinSignals(), BuiltinPolicies(), BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + assert.NotNil(t, actRegistry) + + for _, s := range signals { + outputs := actRegistry.Apply(s) + assert.NotNil(t, outputs) + assert.Len(t, outputs, 2) + assert.Equal(t, "terminate", outputs[0].MatchedPolicy) + assert.Equal(t, outputs[0].Metadata, map[string]any{"terminate": true}) + assert.True(t, outputs[0].Sync) + assert.True(t, cast.ToBool(outputs[0].Verdict)) + assert.True(t, outputs[0].Terminal) + assert.Contains( + t, buf.String(), "Terminal signal takes precedence, ignoring non-terminal signals") + assert.Equal(t, "log", outputs[1].MatchedPolicy) + assert.Equal(t, + map[string]interface{}{ + "async": true, + "level": "info", + "log": true, + "message": "test", + }, + outputs[1].Metadata, + ) + assert.False(t, outputs[1].Sync) + assert.True(t, cast.ToBool(outputs[1].Verdict)) + assert.False(t, outputs[1].Terminal) + } +} + +// Test_Apply_ActionNotMatched tests the Apply function of the act registry +// with a signal that does not match any action. The signal should be ignored. +// The default policy should be applied instead. +func Test_Apply_ActionNotMatched(t *testing.T) { + buf := bytes.Buffer{} + logger := zerolog.New(&buf) + actRegistry := NewActRegistry( + BuiltinSignals(), BuiltinPolicies(), BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + assert.NotNil(t, actRegistry) + + outputs := actRegistry.Apply([]sdkAct.Signal{ + {Name: "non-existent"}, + }) + assert.NotNil(t, outputs) + assert.Len(t, outputs, 1) + assert.Equal(t, "passthrough", outputs[0].MatchedPolicy) + assert.Nil(t, outputs[0].Metadata) + assert.True(t, outputs[0].Sync) + assert.True(t, cast.ToBool(outputs[0].Verdict)) + assert.False(t, outputs[0].Terminal) + assert.Contains(t, buf.String(), "{\"level\":\"error\",\"error\":\"no matching action\",\"name\":\"non-existent\",\"message\":\"Error applying signal\"}") //nolint:lll +} + +// Test_Apply_PolicyNotMatched tests the Apply function of the act registry +// with a signal that does not match any policy. The signal should be ignored. +// The default policy should be applied instead. +func Test_Apply_PolicyNotMatched(t *testing.T) { + buf := bytes.Buffer{} + logger := zerolog.New(&buf) + actRegistry := NewActRegistry( + BuiltinSignals(), + map[string]*sdkAct.Policy{ + "passthrough": BuiltinPolicies()["passthrough"], + }, + BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + assert.NotNil(t, actRegistry) + + outputs := actRegistry.Apply([]sdkAct.Signal{ + *sdkAct.Terminate(), + }) + assert.NotNil(t, outputs) + assert.Len(t, outputs, 1) + assert.Equal(t, "passthrough", outputs[0].MatchedPolicy) + assert.Nil(t, outputs[0].Metadata) + assert.True(t, outputs[0].Sync) + assert.True(t, cast.ToBool(outputs[0].Verdict)) + assert.False(t, outputs[0].Terminal) + assert.Contains(t, buf.String(), "{\"level\":\"error\",\"error\":\"no matching policy\",\"name\":\"terminate\",\"message\":\"Error applying signal\"}") //nolint:lll +} + +// Test_Apply_NonBoolPolicy tests the Apply function of the act registry +// with a non-bool policy. +func Test_Apply_NonBoolPolicy(t *testing.T) { + badPolicies := []map[string]*sdkAct.Policy{ + { + "passthrough": sdkAct.MustNewPolicy( + "passthrough", + "2/0", + nil, + ), + }, + { + "passthrough": sdkAct.MustNewPolicy( + "passthrough", + "2+2", + nil, + ), + }, + } + + for _, policies := range badPolicies { + buf := bytes.Buffer{} + logger := zerolog.New(&buf) + actRegistry := NewActRegistry( + BuiltinSignals(), + policies, + BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + assert.NotNil(t, actRegistry) + + outputs := actRegistry.Apply([]sdkAct.Signal{ + *sdkAct.Passthrough(), + }) + assert.NotNil(t, outputs) + assert.Len(t, outputs, 1) + assert.Equal(t, "passthrough", outputs[0].MatchedPolicy) + assert.Nil(t, outputs[0].Metadata) + assert.True(t, outputs[0].Sync) + assert.NotNil(t, outputs[0].Verdict) + assert.NotEmpty(t, outputs[0].Verdict) + } +} + +// Test_Apply_BadPolicy tests the NewRegistry function with a bad policy, +// which should return a nil registry. +func Test_Apply_BadPolicy(t *testing.T) { + badPolicies := []map[string]*sdkAct.Policy{ + { + "passthrough": sdkAct.MustNewPolicy( + "passthrough", + "2/0 + 'test'", + nil, + ), + }, + { + "passthrough": sdkAct.MustNewPolicy( + "passthrough", + "2+2+true", + nil, + ), + }, + } + + for _, policies := range badPolicies { + buf := bytes.Buffer{} + logger := zerolog.New(&buf) + actRegistry := NewActRegistry( + BuiltinSignals(), + policies, + BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + assert.Nil(t, actRegistry) + } +} + +// Test_Run tests the Run function of the act registry with a non-terminal action. +func Test_Run(t *testing.T) { + logger := zerolog.Logger{} + actRegistry := NewActRegistry( + BuiltinSignals(), BuiltinPolicies(), BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + assert.NotNil(t, actRegistry) + + outputs := actRegistry.Apply([]sdkAct.Signal{ + *sdkAct.Passthrough(), + }) + assert.NotNil(t, outputs) + + result, err := actRegistry.Run(outputs[0], WithLogger(logger)) + assert.Nil(t, err) + assert.True(t, cast.ToBool(result)) +} + +// Test_Run_Terminate tests the Run function of the act registry with a terminal action. +func Test_Run_Terminate(t *testing.T) { + logger := zerolog.Logger{} + actRegistry := NewActRegistry( + BuiltinSignals(), BuiltinPolicies(), BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + assert.NotNil(t, actRegistry) + + outputs := actRegistry.Apply([]sdkAct.Signal{ + *sdkAct.Terminate(), + }) + assert.NotNil(t, outputs) + assert.Equal(t, "terminate", outputs[0].MatchedPolicy) + assert.Equal(t, outputs[0].Metadata, map[string]interface{}{"terminate": true}) + assert.True(t, outputs[0].Sync) + assert.True(t, cast.ToBool(outputs[0].Verdict)) + assert.True(t, outputs[0].Terminal) + + result, err := actRegistry.Run(outputs[0], WithResult(map[string]any{})) + assert.Nil(t, err) + resultMap := cast.ToStringMap(result) + assert.Contains(t, resultMap, "response") + assert.NotEmpty(t, resultMap["response"]) +} + +// Test_Run_Async tests the Run function of the act registry with an asynchronous action. +func Test_Run_Async(t *testing.T) { + out := bytes.Buffer{} + logger := zerolog.New(&out) + actRegistry := NewActRegistry( + BuiltinSignals(), BuiltinPolicies(), BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + assert.NotNil(t, actRegistry) + + outputs := actRegistry.Apply([]sdkAct.Signal{ + *sdkAct.Log("info", "test", map[string]any{"async": true}), + }) + assert.NotNil(t, outputs) + assert.Equal(t, "log", outputs[0].MatchedPolicy) + assert.Equal(t, + map[string]interface{}{ + "async": true, + "level": "info", + "log": true, + "message": "test", + }, + outputs[0].Metadata, + ) + assert.False(t, outputs[0].Sync) + assert.True(t, cast.ToBool(outputs[0].Verdict)) + assert.False(t, outputs[0].Terminal) + + result, err := actRegistry.Run(outputs[0], WithResult(map[string]any{})) + assert.Equal(t, err, gerr.ErrAsyncAction, "expected async action sentinel error") + assert.Nil(t, result, "expected nil result") + + time.Sleep(time.Millisecond) // wait for async action to complete + + // The following is the expected log output from running the async action. + assert.Contains(t, out.String(), "{\"level\":\"debug\",\"action\":\"log\",\"execution_mode\":\"async\",\"message\":\"Running action\"}") //nolint:lll + // The following is the expected log output from the run function of the async action. + assert.Contains(t, out.String(), "{\"level\":\"info\",\"async\":true,\"message\":\"test\"}") +} + +// Test_Run_NilRegistry tests the Run function of the action with a nil output object. +func Test_Run_NilOutput(t *testing.T) { + buf := bytes.Buffer{} + logger := zerolog.New(&buf) + actRegistry := NewActRegistry( + BuiltinSignals(), BuiltinPolicies(), BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + assert.NotNil(t, actRegistry) + + result, err := actRegistry.Run(nil, WithLogger(logger)) + assert.Nil(t, result) + assert.Equal(t, err, gerr.ErrNilPointer) + assert.Contains(t, buf.String(), "Output is nil, run aborted") +} + +// Test_Run_ActionNotExist tests the Run function of the action with an empty output object. +func Test_Run_ActionNotExist(t *testing.T) { + buf := bytes.Buffer{} + logger := zerolog.New(&buf) + actRegistry := NewActRegistry( + BuiltinSignals(), BuiltinPolicies(), BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + assert.NotNil(t, actRegistry) + + result, err := actRegistry.Run(&sdkAct.Output{}, WithLogger(logger)) + assert.Nil(t, result) + assert.Equal(t, err, gerr.ErrActionNotExist) + assert.Contains(t, buf.String(), "Action does not exist, run aborted") +} diff --git a/api/api.go b/api/api.go index 399a73e0..6341bbcf 100644 --- a/api/api.go +++ b/api/api.go @@ -100,7 +100,25 @@ func (a *API) GetGlobalConfig(_ context.Context, group *v1.Group) (*structpb.Str // GetPluginConfig returns the plugin configuration of the GatewayD. func (a *API) GetPluginConfig(context.Context, *emptypb.Empty) (*structpb.Struct, error) { - pluginConfig, err := structpb.NewStruct(a.Config.PluginKoanf.All()) + jsonData, err := json.Marshal(a.Config.PluginKoanf.All()) + if err != nil { + metrics.APIRequestsErrors.WithLabelValues( + "GET", "/v1/GatewayDPluginService/GetPluginConfig", codes.Internal.String(), + ).Inc() + return nil, status.Errorf(codes.Internal, "failed to marshal plugin config: %v", err) + } + + var pluginConfigMap map[string]any + + err = json.Unmarshal(jsonData, &pluginConfigMap) + if err != nil { + metrics.APIRequestsErrors.WithLabelValues( + "GET", "/v1/GatewayDPluginService/GetPluginConfig", codes.Internal.String(), + ).Inc() + return nil, status.Errorf(codes.Internal, "failed to unmarshal plugin config: %v", err) + } + + pluginConfig, err := structpb.NewStruct(pluginConfigMap) if err != nil { metrics.APIRequestsErrors.WithLabelValues( "GET", "/v1/GatewayDPluginService/GetPluginConfig", codes.Internal.String(), diff --git a/api/api_test.go b/api/api_test.go index 7c33c576..158ce6cb 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -6,6 +6,7 @@ import ( "testing" sdkPlugin "github.com/gatewayd-io/gatewayd-plugin-sdk/plugin" + "github.com/gatewayd-io/gatewayd/act" v1 "github.com/gatewayd-io/gatewayd/api/v1" "github.com/gatewayd-io/gatewayd/config" "github.com/gatewayd-io/gatewayd/network" @@ -105,10 +106,13 @@ func TestGetPluginConfig(t *testing.T) { } func TestGetPlugins(t *testing.T) { + actRegistry := act.NewActRegistry( + act.BuiltinSignals(), act.BuiltinPolicies(), act.BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, zerolog.Logger{}) pluginRegistry := plugin.NewRegistry( context.TODO(), + actRegistry, config.Loose, - config.Stop, zerolog.Logger{}, true, ) @@ -131,10 +135,13 @@ func TestGetPlugins(t *testing.T) { } func TestGetPluginsWithEmptyPluginRegistry(t *testing.T) { + actRegistry := act.NewActRegistry( + act.BuiltinSignals(), act.BuiltinPolicies(), act.BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, zerolog.Logger{}) pluginRegistry := plugin.NewRegistry( context.TODO(), + actRegistry, config.Loose, - config.Stop, zerolog.Logger{}, true, ) @@ -237,10 +244,14 @@ func TestGetServers(t *testing.T) { config.DefaultPluginTimeout, ) + actRegistry := act.NewActRegistry( + act.BuiltinSignals(), act.BuiltinPolicies(), act.BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, zerolog.Logger{}) + pluginRegistry := plugin.NewRegistry( context.TODO(), + actRegistry, config.Loose, - config.Stop, zerolog.Logger{}, true, ) diff --git a/api/v1/api.pb.go b/api/v1/api.pb.go index 9315d738..004ced38 100644 --- a/api/v1/api.pb.go +++ b/api/v1/api.pb.go @@ -584,7 +584,7 @@ var file_api_v1_api_proto_rawDesc = []byte{ 0x6e, 0x66, 0x69, 0x67, 0x20, 0x62, 0x79, 0x2e, 0x32, 0x17, 0x7b, 0x22, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x22, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0x7d, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x32, 0xab, 0x26, 0x0a, 0x17, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x41, 0x64, 0x6d, + 0x32, 0x90, 0x26, 0x0a, 0x17, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x41, 0x50, 0x49, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xde, 0x02, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, @@ -690,21 +690,21 @@ var file_api_v1_api_proto_rawDesc = []byte{ 0x30, 0x30, 0x30, 0x30, 0x30, 0x7d, 0x7d, 0x7d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, 0x12, 0x29, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x47, 0x65, 0x74, 0x47, 0x6c, 0x6f, - 0x62, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0xe7, 0x07, 0x0a, 0x0f, 0x47, 0x65, + 0x62, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0xcc, 0x07, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x22, 0xa2, - 0x07, 0x92, 0x41, 0xed, 0x06, 0x2a, 0x0f, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0xd9, 0x06, 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0xd1, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x22, 0x87, + 0x07, 0x92, 0x41, 0xd2, 0x06, 0x2a, 0x0f, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0xbe, 0x06, 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0xb6, 0x06, 0x0a, 0x44, 0x41, 0x20, 0x4a, 0x53, 0x4f, 0x4e, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x69, 0x73, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x20, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x2e, 0x12, 0x1b, 0x0a, 0x19, 0x1a, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, - 0x72, 0x75, 0x63, 0x74, 0x22, 0xeb, 0x05, 0x0a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x12, 0xd6, 0x05, 0x7b, 0x22, 0x63, 0x6f, + 0x72, 0x75, 0x63, 0x74, 0x22, 0xd0, 0x05, 0x0a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x12, 0xbb, 0x05, 0x7b, 0x22, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x3a, 0x22, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x22, 0x2c, 0x22, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x72, @@ -747,168 +747,166 @@ var file_api_v1_api_proto_rawDesc = []byte{ 0x2c, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x22, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x2d, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2d, 0x63, 0x61, 0x63, 0x68, 0x65, 0x22, 0x7d, 0x5d, 0x2c, 0x22, 0x72, 0x65, 0x6c, 0x6f, 0x61, 0x64, 0x4f, 0x6e, 0x43, 0x72, 0x61, 0x73, 0x68, - 0x22, 0x3a, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x22, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x3a, 0x22, 0x73, 0x74, 0x6f, 0x70, - 0x22, 0x2c, 0x22, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x22, 0x3a, 0x22, 0x33, 0x30, 0x73, - 0x22, 0x7d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, 0x12, 0x29, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x61, - 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x2f, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x12, 0xea, 0x07, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, - 0x6e, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x73, 0x22, 0xac, 0x07, 0x92, 0x41, 0xfc, 0x06, 0x2a, 0x0a, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x75, - 0x67, 0x69, 0x6e, 0x73, 0x4a, 0xed, 0x06, 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0xe5, 0x06, 0x0a, - 0x3f, 0x41, 0x20, 0x4a, 0x53, 0x4f, 0x4e, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x69, - 0x73, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x72, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x65, - 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x20, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x2e, - 0x12, 0x19, 0x0a, 0x17, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, - 0x75, 0x67, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x22, 0x86, 0x06, 0x0a, 0x10, - 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, - 0x12, 0xf1, 0x05, 0x7b, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x22, 0x3a, 0x5b, 0x7b, - 0x22, 0x69, 0x64, 0x22, 0x3a, 0x7b, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x22, 0x67, 0x61, - 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x2d, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2d, 0x63, 0x61, - 0x63, 0x68, 0x65, 0x22, 0x2c, 0x22, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, - 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x22, 0x2c, 0x22, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x55, 0x72, - 0x6c, 0x22, 0x3a, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, - 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x2d, 0x69, 0x6f, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, - 0x61, 0x79, 0x64, 0x2d, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2d, 0x63, 0x61, 0x63, 0x68, 0x65, - 0x22, 0x2c, 0x22, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x22, 0x3a, 0x22, 0x2e, 0x2e, - 0x2e, 0x22, 0x7d, 0x2c, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x22, 0x3a, 0x22, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x20, 0x70, 0x6c, 0x75, 0x67, - 0x69, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x63, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x71, - 0x75, 0x65, 0x72, 0x79, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x2c, 0x22, 0x61, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x73, 0x22, 0x3a, 0x5b, 0x22, 0x2e, 0x2e, 0x2e, 0x22, 0x5d, 0x2c, - 0x22, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x22, 0x41, 0x47, 0x50, 0x4c, 0x2d, - 0x33, 0x2e, 0x30, 0x22, 0x2c, 0x22, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x72, 0x6c, - 0x22, 0x3a, 0x22, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x2d, 0x69, - 0x6f, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x2d, 0x70, 0x6c, 0x75, 0x67, 0x69, - 0x6e, 0x2d, 0x63, 0x61, 0x63, 0x68, 0x65, 0x22, 0x2c, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x70, 0x69, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, - 0x22, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x3a, 0x31, 0x38, 0x30, 0x38, 0x30, - 0x22, 0x2c, 0x22, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x44, 0x42, 0x4e, 0x61, 0x6d, 0x65, - 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x65, 0x78, 0x69, 0x74, 0x4f, 0x6e, 0x53, 0x74, 0x61, 0x72, - 0x74, 0x75, 0x70, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x3a, 0x22, 0x46, 0x61, 0x6c, 0x73, 0x65, - 0x22, 0x2c, 0x22, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x22, 0x3a, 0x22, 0x31, 0x68, 0x22, 0x2c, - 0x22, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, - 0x3a, 0x22, 0x54, 0x72, 0x75, 0x65, 0x22, 0x2c, 0x22, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, - 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x3a, 0x22, 0x2f, 0x6d, 0x65, 0x74, 0x72, - 0x69, 0x63, 0x73, 0x22, 0x2c, 0x22, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x55, 0x6e, 0x69, - 0x78, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x22, 0x3a, 0x22, - 0x2f, 0x74, 0x6d, 0x70, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x2d, 0x70, 0x6c, - 0x75, 0x67, 0x69, 0x6e, 0x2d, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x73, 0x6f, 0x63, 0x6b, 0x22, - 0x2c, 0x22, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x69, 0x63, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x6f, 0x72, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x3a, 0x22, 0x54, - 0x72, 0x75, 0x65, 0x22, 0x2c, 0x22, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x69, 0x63, 0x49, 0x6e, - 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, - 0x6c, 0x22, 0x3a, 0x22, 0x31, 0x6d, 0x22, 0x2c, 0x22, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x69, - 0x63, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x72, - 0x74, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x22, 0x3a, 0x22, 0x31, 0x6d, 0x22, 0x2c, 0x22, 0x72, 0x65, - 0x64, 0x69, 0x73, 0x55, 0x52, 0x4c, 0x22, 0x3a, 0x22, 0x72, 0x65, 0x64, 0x69, 0x73, 0x3a, 0x2f, - 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x3a, 0x36, 0x33, 0x37, 0x39, 0x2f, - 0x30, 0x22, 0x2c, 0x22, 0x73, 0x63, 0x61, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x3a, 0x22, - 0x31, 0x30, 0x30, 0x30, 0x22, 0x7d, 0x2c, 0x22, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x22, 0x3a, 0x5b, - 0x31, 0x34, 0x2c, 0x31, 0x36, 0x2c, 0x31, 0x38, 0x5d, 0x2c, 0x22, 0x72, 0x65, 0x71, 0x75, 0x69, - 0x72, 0x65, 0x73, 0x22, 0x3a, 0x7b, 0x7d, 0x2c, 0x22, 0x74, 0x61, 0x67, 0x73, 0x22, 0x3a, 0x5b, - 0x5d, 0x2c, 0x22, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x22, 0x3a, 0x5b, - 0x5d, 0x7d, 0x5d, 0x7d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x12, 0x24, 0x2f, 0x76, 0x31, 0x2f, - 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, - 0x12, 0x93, 0x02, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x73, 0x12, 0x16, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x22, 0xd5, - 0x01, 0x92, 0x41, 0xa7, 0x01, 0x2a, 0x08, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x73, 0x4a, - 0x9a, 0x01, 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0x92, 0x01, 0x0a, 0x3d, 0x41, 0x20, 0x4a, 0x53, - 0x4f, 0x4e, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x69, 0x73, 0x20, 0x72, 0x65, 0x74, - 0x75, 0x72, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x6f, 0x6c, - 0x73, 0x20, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x2e, 0x12, 0x1b, 0x0a, 0x19, 0x1a, 0x17, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x22, 0x34, 0x0a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x12, 0x20, 0x7b, 0x22, 0x64, 0x65, - 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0x3a, 0x7b, 0x22, 0x63, 0x61, 0x70, 0x22, 0x3a, 0x31, 0x30, - 0x2c, 0x22, 0x73, 0x69, 0x7a, 0x65, 0x22, 0x3a, 0x31, 0x30, 0x7d, 0x7d, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x24, 0x12, 0x22, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, - 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x47, 0x65, - 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x73, 0x12, 0xe1, 0x03, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x50, 0x72, - 0x6f, 0x78, 0x69, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x17, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x22, 0xa1, 0x03, 0x92, 0x41, 0xf1, 0x02, 0x2a, 0x0a, 0x47, - 0x65, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x73, 0x4a, 0xe2, 0x02, 0x0a, 0x03, 0x32, 0x30, - 0x30, 0x12, 0xda, 0x02, 0x0a, 0x3f, 0x41, 0x20, 0x4a, 0x53, 0x4f, 0x4e, 0x20, 0x6f, 0x62, 0x6a, + 0x22, 0x3a, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x22, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x22, + 0x3a, 0x22, 0x33, 0x30, 0x73, 0x22, 0x7d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, 0x12, 0x29, 0x2f, + 0x76, 0x31, 0x2f, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x50, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0xea, 0x07, 0x0a, 0x0a, 0x47, 0x65, 0x74, + 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, + 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x22, 0xac, 0x07, 0x92, 0x41, 0xfc, 0x06, 0x2a, 0x0a, 0x47, + 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x4a, 0xed, 0x06, 0x0a, 0x03, 0x32, 0x30, + 0x30, 0x12, 0xe5, 0x06, 0x0a, 0x3f, 0x41, 0x20, 0x4a, 0x53, 0x4f, 0x4e, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x69, 0x73, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x73, 0x20, 0x6d, 0x65, - 0x74, 0x68, 0x6f, 0x64, 0x2e, 0x12, 0x1b, 0x0a, 0x19, 0x1a, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, - 0x63, 0x74, 0x22, 0xf9, 0x01, 0x0a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x12, 0xe4, 0x01, 0x7b, 0x22, 0x64, 0x65, 0x66, 0x61, - 0x75, 0x6c, 0x74, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, - 0x22, 0x3a, 0x5b, 0x22, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x3a, 0x35, 0x30, - 0x39, 0x39, 0x32, 0x22, 0x2c, 0x22, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x3a, - 0x35, 0x30, 0x39, 0x35, 0x36, 0x22, 0x2c, 0x22, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, - 0x31, 0x3a, 0x35, 0x31, 0x30, 0x30, 0x36, 0x22, 0x2c, 0x22, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, - 0x30, 0x2e, 0x31, 0x3a, 0x35, 0x30, 0x39, 0x37, 0x32, 0x22, 0x2c, 0x22, 0x31, 0x32, 0x37, 0x2e, - 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x3a, 0x35, 0x31, 0x30, 0x30, 0x32, 0x22, 0x2c, 0x22, 0x31, 0x32, - 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x3a, 0x35, 0x30, 0x39, 0x38, 0x30, 0x22, 0x2c, 0x22, - 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x3a, 0x35, 0x30, 0x39, 0x33, 0x30, 0x22, - 0x2c, 0x22, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x3a, 0x35, 0x30, 0x39, 0x34, - 0x36, 0x22, 0x2c, 0x22, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x3a, 0x35, 0x30, - 0x39, 0x39, 0x36, 0x22, 0x2c, 0x22, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x3a, - 0x35, 0x31, 0x30, 0x32, 0x32, 0x22, 0x5d, 0x2c, 0x22, 0x62, 0x75, 0x73, 0x79, 0x22, 0x3a, 0x5b, - 0x5d, 0x2c, 0x22, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x3a, 0x31, 0x30, 0x7d, 0x7d, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x26, 0x12, 0x24, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, - 0x79, 0x44, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, - 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x73, 0x12, 0xd7, 0x02, 0x0a, 0x0a, 0x47, - 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x1a, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x22, 0x97, 0x02, 0x92, 0x41, 0xe7, - 0x01, 0x2a, 0x0a, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x4a, 0xd8, 0x01, - 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0xd0, 0x01, 0x0a, 0x3f, 0x41, 0x20, 0x4a, 0x53, 0x4f, 0x4e, - 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x69, 0x73, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, - 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x20, - 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x73, 0x20, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x2e, 0x12, 0x1b, 0x0a, 0x19, 0x1a, 0x17, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x22, 0x70, 0x0a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x12, 0x5c, 0x7b, 0x22, 0x64, 0x65, - 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x22, 0x3a, 0x22, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x3a, 0x31, 0x35, 0x34, 0x33, 0x32, - 0x22, 0x2c, 0x22, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x22, 0x3a, 0x22, 0x74, 0x63, 0x70, - 0x22, 0x2c, 0x22, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x74, 0x69, - 0x63, 0x6b, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x22, 0x3a, 0x35, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x7d, 0x7d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x12, 0x24, - 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x50, 0x6c, 0x75, 0x67, - 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x73, 0x1a, 0x58, 0x92, 0x41, 0x55, 0x12, 0x23, 0x47, 0x61, 0x74, 0x65, 0x77, - 0x61, 0x79, 0x44, 0x20, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x20, 0x41, 0x50, 0x49, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x1a, 0x2e, - 0x12, 0x2c, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x64, 0x6f, 0x63, 0x73, 0x2e, 0x67, - 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x2e, 0x69, 0x6f, 0x2f, 0x75, 0x73, 0x69, 0x6e, 0x67, - 0x2d, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x2f, 0x41, 0x50, 0x49, 0x2f, 0x42, 0x8b, - 0x02, 0x92, 0x41, 0xdf, 0x01, 0x12, 0xc7, 0x01, 0x0a, 0x12, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, - 0x79, 0x44, 0x20, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x20, 0x41, 0x50, 0x49, 0x22, 0x45, 0x0a, 0x08, - 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x12, 0x27, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, - 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x74, - 0x65, 0x77, 0x61, 0x79, 0x64, 0x2d, 0x69, 0x6f, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, - 0x64, 0x1a, 0x10, 0x69, 0x6e, 0x66, 0x6f, 0x40, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, - 0x2e, 0x69, 0x6f, 0x2a, 0x63, 0x0a, 0x26, 0x47, 0x4e, 0x55, 0x20, 0x41, 0x66, 0x66, 0x65, 0x72, - 0x6f, 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x20, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x76, 0x33, 0x2e, 0x30, 0x12, 0x39, 0x68, + 0x68, 0x65, 0x20, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x20, 0x6d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x2e, 0x12, 0x19, 0x0a, 0x17, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, + 0x22, 0x86, 0x06, 0x0a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x12, 0xf1, 0x05, 0x7b, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x69, 0x64, 0x22, 0x3a, 0x7b, 0x22, 0x6e, 0x61, 0x6d, 0x65, + 0x22, 0x3a, 0x22, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x2d, 0x70, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x2d, 0x63, 0x61, 0x63, 0x68, 0x65, 0x22, 0x2c, 0x22, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x22, 0x2c, 0x22, 0x72, 0x65, 0x6d, + 0x6f, 0x74, 0x65, 0x55, 0x72, 0x6c, 0x22, 0x3a, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x2d, 0x69, 0x6f, 0x2f, + 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x2d, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2d, + 0x63, 0x61, 0x63, 0x68, 0x65, 0x22, 0x2c, 0x22, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, + 0x22, 0x3a, 0x22, 0x2e, 0x2e, 0x2e, 0x22, 0x7d, 0x2c, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, + 0x20, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x63, 0x61, 0x63, 0x68, + 0x69, 0x6e, 0x67, 0x20, 0x71, 0x75, 0x65, 0x72, 0x79, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x73, 0x22, 0x2c, 0x22, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x73, 0x22, 0x3a, 0x5b, 0x22, 0x2e, + 0x2e, 0x2e, 0x22, 0x5d, 0x2c, 0x22, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x22, + 0x41, 0x47, 0x50, 0x4c, 0x2d, 0x33, 0x2e, 0x30, 0x22, 0x2c, 0x22, 0x70, 0x72, 0x6f, 0x6a, 0x65, + 0x63, 0x74, 0x55, 0x72, 0x6c, 0x22, 0x3a, 0x22, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, + 0x61, 0x79, 0x64, 0x2d, 0x69, 0x6f, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x2d, + 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2d, 0x63, 0x61, 0x63, 0x68, 0x65, 0x22, 0x2c, 0x22, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x70, 0x69, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x22, 0x3a, 0x22, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x3a, + 0x31, 0x38, 0x30, 0x38, 0x30, 0x22, 0x2c, 0x22, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x44, + 0x42, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x65, 0x78, 0x69, 0x74, 0x4f, + 0x6e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x3a, 0x22, + 0x46, 0x61, 0x6c, 0x73, 0x65, 0x22, 0x2c, 0x22, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x22, 0x3a, + 0x22, 0x31, 0x68, 0x22, 0x2c, 0x22, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x64, 0x22, 0x3a, 0x22, 0x54, 0x72, 0x75, 0x65, 0x22, 0x2c, 0x22, 0x6d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x73, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x3a, 0x22, + 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x22, 0x2c, 0x22, 0x6d, 0x65, 0x74, 0x72, 0x69, + 0x63, 0x73, 0x55, 0x6e, 0x69, 0x78, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x6f, 0x63, 0x6b, + 0x65, 0x74, 0x22, 0x3a, 0x22, 0x2f, 0x74, 0x6d, 0x70, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, + 0x79, 0x64, 0x2d, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2d, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2e, + 0x73, 0x6f, 0x63, 0x6b, 0x22, 0x2c, 0x22, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x69, 0x63, 0x49, + 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x22, 0x3a, 0x22, 0x54, 0x72, 0x75, 0x65, 0x22, 0x2c, 0x22, 0x70, 0x65, 0x72, 0x69, 0x6f, + 0x64, 0x69, 0x63, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x6e, + 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x22, 0x3a, 0x22, 0x31, 0x6d, 0x22, 0x2c, 0x22, 0x70, 0x65, + 0x72, 0x69, 0x6f, 0x64, 0x69, 0x63, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, + 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x22, 0x3a, 0x22, 0x31, 0x6d, + 0x22, 0x2c, 0x22, 0x72, 0x65, 0x64, 0x69, 0x73, 0x55, 0x52, 0x4c, 0x22, 0x3a, 0x22, 0x72, 0x65, + 0x64, 0x69, 0x73, 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x3a, + 0x36, 0x33, 0x37, 0x39, 0x2f, 0x30, 0x22, 0x2c, 0x22, 0x73, 0x63, 0x61, 0x6e, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x22, 0x3a, 0x22, 0x31, 0x30, 0x30, 0x30, 0x22, 0x7d, 0x2c, 0x22, 0x68, 0x6f, 0x6f, + 0x6b, 0x73, 0x22, 0x3a, 0x5b, 0x31, 0x34, 0x2c, 0x31, 0x36, 0x2c, 0x31, 0x38, 0x5d, 0x2c, 0x22, + 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x22, 0x3a, 0x7b, 0x7d, 0x2c, 0x22, 0x74, 0x61, + 0x67, 0x73, 0x22, 0x3a, 0x5b, 0x5d, 0x2c, 0x22, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, + 0x65, 0x73, 0x22, 0x3a, 0x5b, 0x5d, 0x7d, 0x5d, 0x7d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x12, + 0x24, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x50, 0x6c, 0x75, + 0x67, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x47, 0x65, 0x74, 0x50, 0x6c, + 0x75, 0x67, 0x69, 0x6e, 0x73, 0x12, 0x93, 0x02, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x6f, + 0x6c, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x17, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, + 0x75, 0x63, 0x74, 0x22, 0xd5, 0x01, 0x92, 0x41, 0xa7, 0x01, 0x2a, 0x08, 0x47, 0x65, 0x74, 0x50, + 0x6f, 0x6f, 0x6c, 0x73, 0x4a, 0x9a, 0x01, 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0x92, 0x01, 0x0a, + 0x3d, 0x41, 0x20, 0x4a, 0x53, 0x4f, 0x4e, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x69, + 0x73, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x72, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x65, + 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x73, 0x20, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x2e, 0x12, 0x1b, + 0x0a, 0x19, 0x1a, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x22, 0x34, 0x0a, 0x10, 0x61, + 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x12, + 0x20, 0x7b, 0x22, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0x3a, 0x7b, 0x22, 0x63, 0x61, + 0x70, 0x22, 0x3a, 0x31, 0x30, 0x2c, 0x22, 0x73, 0x69, 0x7a, 0x65, 0x22, 0x3a, 0x31, 0x30, 0x7d, + 0x7d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x12, 0x22, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x61, 0x74, + 0x65, 0x77, 0x61, 0x79, 0x44, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x2f, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x73, 0x12, 0xe1, 0x03, 0x0a, 0x0a, + 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x1a, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x22, 0xa1, 0x03, 0x92, 0x41, + 0xf1, 0x02, 0x2a, 0x0a, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x73, 0x4a, 0xe2, + 0x02, 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0xda, 0x02, 0x0a, 0x3f, 0x41, 0x20, 0x4a, 0x53, 0x4f, + 0x4e, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x69, 0x73, 0x20, 0x72, 0x65, 0x74, 0x75, + 0x72, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x69, + 0x65, 0x73, 0x20, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x2e, 0x12, 0x1b, 0x0a, 0x19, 0x1a, 0x17, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x22, 0xf9, 0x01, 0x0a, 0x10, 0x61, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x12, 0xe4, 0x01, 0x7b, + 0x22, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x76, 0x61, 0x69, + 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x3a, 0x5b, 0x22, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, + 0x2e, 0x31, 0x3a, 0x35, 0x30, 0x39, 0x39, 0x32, 0x22, 0x2c, 0x22, 0x31, 0x32, 0x37, 0x2e, 0x30, + 0x2e, 0x30, 0x2e, 0x31, 0x3a, 0x35, 0x30, 0x39, 0x35, 0x36, 0x22, 0x2c, 0x22, 0x31, 0x32, 0x37, + 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x3a, 0x35, 0x31, 0x30, 0x30, 0x36, 0x22, 0x2c, 0x22, 0x31, + 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x3a, 0x35, 0x30, 0x39, 0x37, 0x32, 0x22, 0x2c, + 0x22, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x3a, 0x35, 0x31, 0x30, 0x30, 0x32, + 0x22, 0x2c, 0x22, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x3a, 0x35, 0x30, 0x39, + 0x38, 0x30, 0x22, 0x2c, 0x22, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x3a, 0x35, + 0x30, 0x39, 0x33, 0x30, 0x22, 0x2c, 0x22, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, + 0x3a, 0x35, 0x30, 0x39, 0x34, 0x36, 0x22, 0x2c, 0x22, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, + 0x2e, 0x31, 0x3a, 0x35, 0x30, 0x39, 0x39, 0x36, 0x22, 0x2c, 0x22, 0x31, 0x32, 0x37, 0x2e, 0x30, + 0x2e, 0x30, 0x2e, 0x31, 0x3a, 0x35, 0x31, 0x30, 0x32, 0x32, 0x22, 0x5d, 0x2c, 0x22, 0x62, 0x75, + 0x73, 0x79, 0x22, 0x3a, 0x5b, 0x5d, 0x2c, 0x22, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0x3a, 0x31, + 0x30, 0x7d, 0x7d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x12, 0x24, 0x2f, 0x76, 0x31, 0x2f, 0x47, + 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x2f, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x73, 0x12, + 0xd7, 0x02, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x22, + 0x97, 0x02, 0x92, 0x41, 0xe7, 0x01, 0x2a, 0x0a, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x73, 0x4a, 0xd8, 0x01, 0x0a, 0x03, 0x32, 0x30, 0x30, 0x12, 0xd0, 0x01, 0x0a, 0x3f, 0x41, + 0x20, 0x4a, 0x53, 0x4f, 0x4e, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x69, 0x73, 0x20, + 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x72, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x65, 0x74, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x20, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x2e, 0x12, 0x1b, + 0x0a, 0x19, 0x1a, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x22, 0x70, 0x0a, 0x10, 0x61, + 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x12, + 0x5c, 0x7b, 0x22, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x22, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x3a, + 0x31, 0x35, 0x34, 0x33, 0x32, 0x22, 0x2c, 0x22, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x22, + 0x3a, 0x22, 0x74, 0x63, 0x70, 0x22, 0x2c, 0x22, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x3a, + 0x30, 0x2c, 0x22, 0x74, 0x69, 0x63, 0x6b, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x22, + 0x3a, 0x35, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x7d, 0x7d, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x26, 0x12, 0x24, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, + 0x44, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x47, + 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x1a, 0x58, 0x92, 0x41, 0x55, 0x12, 0x23, + 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x20, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, + 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x50, 0x49, 0x20, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x1a, 0x2e, 0x12, 0x2c, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x64, + 0x6f, 0x63, 0x73, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x2e, 0x69, 0x6f, 0x2f, + 0x75, 0x73, 0x69, 0x6e, 0x67, 0x2d, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x2f, 0x41, + 0x50, 0x49, 0x2f, 0x42, 0x8b, 0x02, 0x92, 0x41, 0xdf, 0x01, 0x12, 0xc7, 0x01, 0x0a, 0x12, 0x47, + 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x20, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x20, 0x41, 0x50, + 0x49, 0x22, 0x45, 0x0a, 0x08, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x12, 0x27, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x2d, 0x69, 0x6f, 0x2f, 0x67, 0x61, - 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x6d, 0x61, 0x69, 0x6e, - 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, 0x05, 0x31, 0x2e, 0x30, 0x2e, 0x30, 0x2a, - 0x01, 0x01, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, - 0x6a, 0x73, 0x6f, 0x6e, 0x5a, 0x26, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x2d, 0x69, 0x6f, 0x2f, 0x67, 0x61, 0x74, - 0x65, 0x77, 0x61, 0x79, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x1a, 0x10, 0x69, 0x6e, 0x66, 0x6f, 0x40, 0x67, 0x61, 0x74, + 0x65, 0x77, 0x61, 0x79, 0x64, 0x2e, 0x69, 0x6f, 0x2a, 0x63, 0x0a, 0x26, 0x47, 0x4e, 0x55, 0x20, + 0x41, 0x66, 0x66, 0x65, 0x72, 0x6f, 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x76, 0x33, + 0x2e, 0x30, 0x12, 0x39, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x2d, + 0x69, 0x6f, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x2f, 0x62, 0x6c, 0x6f, 0x62, + 0x2f, 0x6d, 0x61, 0x69, 0x6e, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, 0x05, 0x31, + 0x2e, 0x30, 0x2e, 0x30, 0x2a, 0x01, 0x01, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x5a, 0x26, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x2d, 0x69, + 0x6f, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, + 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/api/v1/api.proto b/api/v1/api.proto index ed155bbe..2505dfcf 100644 --- a/api/v1/api.proto +++ b/api/v1/api.proto @@ -87,7 +87,7 @@ service GatewayDAdminAPIService { }, examples: { key: "application/json" - value: '{"compatibilityPolicy":"strict","enableMetricsMerger":true,"healthCheckPeriod":"5s","metricsMergerPeriod":"5s","plugins":[{"args":["--log-level","debug"],"checksum":"...","enabled":true,"env":["MAGIC_COOKIE_KEY=...","MAGIC_COOKIE_VALUE=...","REDIS_URL=redis://localhost:6379/0","EXPIRY=1h","METRICS_ENABLED=True","METRICS_UNIX_DOMAIN_SOCKET=/tmp/gatewayd-plugin-cache.sock","METRICS_PATH=/metrics","PERIODIC_INVALIDATOR_ENABLED=True","PERIODIC_INVALIDATOR_INTERVAL=1m","PERIODIC_INVALIDATOR_START_DELAY=1m","API_ADDRESS=localhost:18080","EXIT_ON_STARTUP_ERROR=False","SENTRY_DSN=..."],"localPath":"plugins/gatewayd-plugin-cache","name":"gatewayd-plugin-cache"}],"reloadOnCrash":true,"terminationPolicy":"stop","timeout":"30s"}' + value: '{"compatibilityPolicy":"strict","enableMetricsMerger":true,"healthCheckPeriod":"5s","metricsMergerPeriod":"5s","plugins":[{"args":["--log-level","debug"],"checksum":"...","enabled":true,"env":["MAGIC_COOKIE_KEY=...","MAGIC_COOKIE_VALUE=...","REDIS_URL=redis://localhost:6379/0","EXPIRY=1h","METRICS_ENABLED=True","METRICS_UNIX_DOMAIN_SOCKET=/tmp/gatewayd-plugin-cache.sock","METRICS_PATH=/metrics","PERIODIC_INVALIDATOR_ENABLED=True","PERIODIC_INVALIDATOR_INTERVAL=1m","PERIODIC_INVALIDATOR_START_DELAY=1m","API_ADDRESS=localhost:18080","EXIT_ON_STARTUP_ERROR=False","SENTRY_DSN=..."],"localPath":"plugins/gatewayd-plugin-cache","name":"gatewayd-plugin-cache"}],"reloadOnCrash":true,"timeout":"30s"}' } }; }; diff --git a/api/v1/api.swagger.json b/api/v1/api.swagger.json index d68871d6..e7d6213b 100644 --- a/api/v1/api.swagger.json +++ b/api/v1/api.swagger.json @@ -183,7 +183,6 @@ } ], "reloadOnCrash": true, - "terminationPolicy": "stop", "timeout": "30s" } } diff --git a/cmd/run.go b/cmd/run.go index 0e7ccc95..7ad42f48 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -16,8 +16,10 @@ import ( "time" "github.com/NYTimes/gziphandler" + sdkAct "github.com/gatewayd-io/gatewayd-plugin-sdk/act" sdkPlugin "github.com/gatewayd-io/gatewayd-plugin-sdk/plugin" v1 "github.com/gatewayd-io/gatewayd-plugin-sdk/plugin/v1" + "github.com/gatewayd-io/gatewayd/act" "github.com/gatewayd-io/gatewayd/api" "github.com/gatewayd-io/gatewayd/config" gerr "github.com/gatewayd-io/gatewayd/errors" @@ -37,6 +39,7 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" + "golang.org/x/exp/maps" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) @@ -54,6 +57,7 @@ var ( globalConfigFile string conf *config.Config pluginRegistry *plugin.Registry + actRegistry *act.Registry metricsServer *http.Server UsageReportURL = "localhost:59091" @@ -249,20 +253,42 @@ var runCmd = &cobra.Command{ "Running GatewayD in development mode (not recommended for production)") } + // Create a new act registry given the built-in signals, policies, and actions. + actRegistry = act.NewActRegistry( + act.BuiltinSignals(), act.BuiltinPolicies(), act.BuiltinActions(), + conf.Plugin.DefaultPolicy, conf.Plugin.PolicyTimeout, logger, + ) + + if actRegistry == nil { + logger.Error().Msg("Failed to create act registry") + os.Exit(gerr.FailedToCreateActRegistry) + } + + // Load policies from the configuration file and add them to the registry. + for _, plc := range conf.Plugin.Policies { + if policy, err := sdkAct.NewPolicy( + plc.Name, plc.Policy, plc.Metadata, + ); err != nil || policy == nil { + logger.Error().Err(err).Str("name", plc.Name).Msg("Failed to create policy") + } else { + actRegistry.Add(policy) + } + } + + logger.Info().Fields(map[string]interface{}{ + "policies": maps.Keys(actRegistry.Policies), + }).Msg("Policies are loaded") + // Create a new plugin registry. // The plugins are loaded and hooks registered before the configuration is loaded. pluginRegistry = plugin.NewRegistry( runCtx, + actRegistry, config.If[config.CompatibilityPolicy]( config.Exists[string, config.CompatibilityPolicy]( config.CompatibilityPolicies, conf.Plugin.CompatibilityPolicy), config.CompatibilityPolicies[conf.Plugin.CompatibilityPolicy], config.DefaultCompatibilityPolicy), - config.If[config.TerminationPolicy]( - config.Exists[string, config.TerminationPolicy]( - config.TerminationPolicies, conf.Plugin.TerminationPolicy), - config.TerminationPolicies[conf.Plugin.TerminationPolicy], - config.DefaultTerminationPolicy), logger, devMode, ) diff --git a/config/config.go b/config/config.go index 3ddb0714..b0389259 100644 --- a/config/config.go +++ b/config/config.go @@ -206,13 +206,15 @@ func (c *Config) LoadDefaults(ctx context.Context) { c.pluginDefaults = PluginConfig{ CompatibilityPolicy: string(Strict), - TerminationPolicy: string(Stop), EnableMetricsMerger: true, MetricsMergerPeriod: DefaultMetricsMergerPeriod, HealthCheckPeriod: DefaultPluginHealthCheckPeriod, ReloadOnCrash: true, Timeout: DefaultPluginTimeout, StartTimeout: DefaultPluginStartTimeout, + DefaultPolicy: DefaultPolicy, + PolicyTimeout: DefaultPolicyTimeout, + Policies: []Policy{}, } if c.GlobalKoanf != nil { diff --git a/config/constants.go b/config/constants.go index a698bd6e..3fe9d5d5 100644 --- a/config/constants.go +++ b/config/constants.go @@ -7,7 +7,6 @@ import ( type ( Status uint CompatibilityPolicy string - TerminationPolicy string LogOutput uint ) @@ -23,13 +22,6 @@ const ( Loose CompatibilityPolicy = "loose" // Load the plugin, even if the requirements are not met ) -// TerminationPolicy is the termination policy for -// the functions registered to the OnTrafficFromClient hook. -const ( - Continue TerminationPolicy = "continue" // Continue to the next function - Stop TerminationPolicy = "stop" // Stop the execution of the functions -) - // LogOutput is the output type for the logger. const ( Console LogOutput = iota @@ -129,5 +121,8 @@ const ( // Policies. DefaultCompatibilityPolicy = Strict - DefaultTerminationPolicy = Stop + + // Act. + DefaultPolicy = "passthrough" + DefaultPolicyTimeout = 30 * time.Second ) diff --git a/config/getters.go b/config/getters.go index b7587705..406ec208 100644 --- a/config/getters.go +++ b/config/getters.go @@ -13,10 +13,6 @@ var ( "strict": Strict, "loose": Loose, } - TerminationPolicies = map[string]TerminationPolicy{ - "continue": Continue, - "stop": Stop, - } logOutputs = map[string]LogOutput{ "console": Console, "stdout": Stdout, diff --git a/config/types.go b/config/types.go index aaa4a3fd..fd7ceecd 100644 --- a/config/types.go +++ b/config/types.go @@ -15,9 +15,14 @@ type Plugin struct { URL string `json:"url"` } +type Policy struct { + Name string `json:"name" jsonschema:"required"` + Policy string `json:"policy" jsonschema:"required"` + Metadata map[string]any `json:"metadata,omitempty"` +} + type PluginConfig struct { CompatibilityPolicy string `json:"compatibilityPolicy" jsonschema:"enum=strict,enum=loose"` - TerminationPolicy string `json:"terminationPolicy" jsonschema:"enum=continue,enum=stop"` EnableMetricsMerger bool `json:"enableMetricsMerger"` MetricsMergerPeriod time.Duration `json:"metricsMergerPeriod" jsonschema:"oneof_type=string;integer"` HealthCheckPeriod time.Duration `json:"healthCheckPeriod" jsonschema:"oneof_type=string;integer"` @@ -25,6 +30,9 @@ type PluginConfig struct { Timeout time.Duration `json:"timeout" jsonschema:"oneof_type=string;integer"` StartTimeout time.Duration `json:"startTimeout" jsonschema:"oneof_type=string;integer"` Plugins []Plugin `json:"plugins"` + DefaultPolicy string `json:"defaultPolicy" jsonschema:"enum=passthrough,enum=terminate"` // TODO: Add more policies. + PolicyTimeout time.Duration `json:"policyTimeout" jsonschema:"oneof_type=string;integer"` + Policies []Policy `json:"policies"` } type Client struct { diff --git a/errors/errors.go b/errors/errors.go index 896ab170..c7cf7ae1 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -1,5 +1,7 @@ package errors +import "errors" + const ( ErrCodeUnknown ErrCode = iota ErrCodeNilContext @@ -41,6 +43,10 @@ const ( ErrCodeLintingFailed ErrCodeExtractFailed ErrCodeDownloadFailed + ErrCodeKeyNotFound + ErrCodeRunError + ErrCodeAsyncAction + ErrCodeEvalError ) var ( @@ -133,13 +139,30 @@ var ( ErrCodeExtractFailed, "failed to extract the archive", nil) ErrDownloadFailed = NewGatewayDError( ErrCodeDownloadFailed, "failed to download the file", nil) + + ErrActionNotExist = NewGatewayDError( + ErrCodeKeyNotFound, "action does not exist", nil) + ErrRunningAction = NewGatewayDError( + ErrCodeRunError, "error running action", nil) + ErrAsyncAction = NewGatewayDError( + ErrCodeAsyncAction, "async action", nil) + ErrActionNotMatched = NewGatewayDError( + ErrCodeKeyNotFound, "no matching action", nil) + ErrPolicyNotMatched = NewGatewayDError( + ErrCodeKeyNotFound, "no matching policy", nil) + ErrEvalError = NewGatewayDError( + ErrCodeEvalError, "error evaluating expression", nil) + + // Unwrapped errors. + ErrLoggerRequired = errors.New("terminate action requires a logger parameter") ) const ( - FailedToLoadPluginConfig = 1 - FailedToLoadGlobalConfig = 2 - FailedToCreateClient = 3 - FailedToInitializePool = 4 - FailedToStartServer = 5 - FailedToStartTracer = 6 + FailedToLoadPluginConfig = 1 + FailedToLoadGlobalConfig = 2 + FailedToCreateClient = 3 + FailedToInitializePool = 4 + FailedToStartServer = 5 + FailedToStartTracer = 6 + FailedToCreateActRegistry = 7 ) diff --git a/gatewayd_plugins.yaml b/gatewayd_plugins.yaml index ae229d1f..09178a6a 100644 --- a/gatewayd_plugins.yaml +++ b/gatewayd_plugins.yaml @@ -9,17 +9,6 @@ # plugin and that version is not the one currently loaded. compatibilityPolicy: "strict" -# The termination policy controls how to handle the termination of requests. If a plugin -# terminates a request, the termination policy controls whether to stop executing the -# remaining plugins or not. If the termination policy is set to "stop", the remaining plugins -# are not executed. If the termination policy is set to "continue", the remaining plugins are -# executed. Warning: if the termination policy is set to "continue", the output of the -# remaining plugins might be passed down to the next plugin, and the result depends on the -# what the remaining plugins do. -# - "stop" (default): the remaining plugins are not executed. -# - "continue": the remaining plugins are executed. -terminationPolicy: "stop" - # The metrics policy controls whether to collect and merge metrics from plugins or not. # The Prometheus metrics are collected from the plugins via a Unix domain socket. The metrics # are merged and exposed via the GatewayD metrics endpoint via HTTP. @@ -42,6 +31,12 @@ timeout: 30s # The start timeout controls how long to wait for a plugin to start before timing out. startTimeout: 1m +# The policy timeout controls how long to wait for the evluation of the policy before timing out. +policyTimeout: 30s + +# The policy is a list of policies to apply to the signals received from the plugins. +policies: [] + # The plugin configuration is a list of plugins to load. Each plugin is defined by a name, # a path to the plugin's executable, and a list of arguments to pass to the plugin. The # plugin's executable is expected to be a Go plugin that implements the GatewayD plugin diff --git a/go.mod b/go.mod index 87ead362..268f5779 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,13 @@ module github.com/gatewayd-io/gatewayd -go 1.21 +go 1.22 require ( github.com/Masterminds/semver/v3 v3.2.1 github.com/NYTimes/gziphandler v1.1.1 github.com/codingsince1985/checksum v1.3.0 github.com/envoyproxy/protoc-gen-validate v1.0.4 - github.com/gatewayd-io/gatewayd-plugin-sdk v0.1.10 + github.com/gatewayd-io/gatewayd-plugin-sdk v0.2.5 github.com/getsentry/sentry-go v0.27.0 github.com/go-co-op/gocron v1.37.0 github.com/google/go-github/v53 v53.2.0 @@ -15,23 +15,24 @@ require ( github.com/hashicorp/go-hclog v1.6.2 github.com/hashicorp/go-plugin v1.6.0 github.com/invopop/jsonschema v0.12.0 + github.com/jackc/pgx/v5 v5.5.3 github.com/knadh/koanf v1.5.0 github.com/mitchellh/mapstructure v1.5.0 - github.com/prometheus/client_golang v1.18.0 + github.com/prometheus/client_golang v1.19.0 github.com/prometheus/client_model v0.6.0 - github.com/prometheus/common v0.47.0 + github.com/prometheus/common v0.48.0 github.com/rs/zerolog v1.32.0 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/spf13/cast v1.6.0 github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.8.4 github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04 - go.opentelemetry.io/otel v1.23.1 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1 - go.opentelemetry.io/otel/sdk v1.23.1 - go.opentelemetry.io/otel/trace v1.23.1 - golang.org/x/exp v0.0.0-20240213143201-ec583247a57a + go.opentelemetry.io/otel v1.24.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 + go.opentelemetry.io/otel/sdk v1.24.0 + go.opentelemetry.io/otel/trace v1.24.0 + golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c google.golang.org/grpc v1.62.0 google.golang.org/protobuf v1.32.0 @@ -48,6 +49,7 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/expr-lang/expr v1.16.1 // indirect github.com/fatih/color v1.16.0 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect @@ -66,15 +68,18 @@ require ( github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/oklog/run v1.1.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pganalyze/pg_query_go/v5 v5.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/tetratelabs/wazero v1.6.1-0.20240124004658-4185e533bb18 // indirect + github.com/wasilibs/go-pgquery v0.0.0-20240124010238-c9a912d768dc // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect - go.opentelemetry.io/otel/metric v1.23.1 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/proto/otlp v1.1.0 // indirect go.uber.org/atomic v1.11.0 // indirect - golang.org/x/crypto v0.19.0 // indirect + golang.org/x/crypto v0.20.0 // indirect golang.org/x/net v0.21.0 // indirect golang.org/x/oauth2 v0.17.0 // indirect golang.org/x/sys v0.17.0 // indirect diff --git a/go.sum b/go.sum index 90256b69..26ec896b 100644 --- a/go.sum +++ b/go.sum @@ -69,6 +69,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/expr-lang/expr v1.16.1 h1:Na8CUcMdyGbnNpShY7kzcHCU7WqxuL+hnxgHZ4vaz/A= +github.com/expr-lang/expr v1.16.1/go.mod h1:uCkhfG+x7fcZ5A5sXHKuQ07jGZRl6J0FCAaf2k4PtVQ= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= @@ -81,8 +83,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/gatewayd-io/gatewayd-plugin-sdk v0.1.10 h1:jtHzHtekVEK13d6hqleFZ41D8SWABF4R2JHhOHFQOmc= -github.com/gatewayd-io/gatewayd-plugin-sdk v0.1.10/go.mod h1:AS9WVWdp0av0D2sZkRM9ZOZMMp3DpNnwDsnmwNGCCrM= +github.com/gatewayd-io/gatewayd-plugin-sdk v0.2.5 h1:H1S4CKS4IfezxlvgBLtSJ/3s85wznxgxJEnwLys+kIM= +github.com/gatewayd-io/gatewayd-plugin-sdk v0.2.5/go.mod h1:1XS2ufw+8VRTHAbDf18Y7rSPlOczeQ/baUWPqJrDkeE= github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -198,6 +200,12 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.3 h1:Ces6/M3wbDXYpM8JyyPD57ivTtJACFZJd885pdIaV2s= +github.com/jackc/pgx/v5 v5.5.3/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -284,6 +292,8 @@ github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144T github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pganalyze/pg_query_go/v5 v5.1.0 h1:MlxQqHZnvA3cbRQYyIrjxEjzo560P6MyTgtlaf3pmXg= +github.com/pganalyze/pg_query_go/v5 v5.1.0/go.mod h1:FsglvxidZsVN+Ltw3Ai6nTgPVcK2BPukH3jCDEqc1Ug= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= @@ -300,8 +310,8 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= -github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= +github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -311,8 +321,8 @@ github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOA github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.47.0 h1:p5Cz0FNHo7SnWOmWmoRozVcjEp0bIVU8cV7OShpjL1k= -github.com/prometheus/common v0.47.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= @@ -361,6 +371,10 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tetratelabs/wazero v1.6.1-0.20240124004658-4185e533bb18 h1:Gi/arySP4fsMGdfv1uLMBZ59P4trxQVybzo/jEmqSOE= +github.com/tetratelabs/wazero v1.6.1-0.20240124004658-4185e533bb18/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A= +github.com/wasilibs/go-pgquery v0.0.0-20240124010238-c9a912d768dc h1:maN7B5k6qQd8JwyW9W4UjZ9J+30MNn1phiM5GeKdy+g= +github.com/wasilibs/go-pgquery v0.0.0-20240124010238-c9a912d768dc/go.mod h1:EdrSnP/ky2/FikNtQkVR+dTESNjbeY9TqLlxsRCddC8= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -372,18 +386,18 @@ github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04/go.mod h1:FiwNQ go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= -go.opentelemetry.io/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY= -go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1 h1:o8iWeVFa1BcLtVEV0LzrCxV2/55tB3xLxADr6Kyoey4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1/go.mod h1:SEVfdK4IoBnbT2FXNM/k8yC08MrfbhWk3U4ljM8B3HE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1 h1:p3A5+f5l9e/kuEBwLOrnpkIDHQFlHmbiVxMURWRK6gQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1/go.mod h1:OClrnXUjBqQbInvjJFjYSnMxBSCXBF8r3b34WqjiIrQ= -go.opentelemetry.io/otel/metric v1.23.1 h1:PQJmqJ9u2QaJLBOELl1cxIdPcpbwzbkjfEyelTl2rlo= -go.opentelemetry.io/otel/metric v1.23.1/go.mod h1:mpG2QPlAfnK8yNhNJAxDZruU9Y1/HubbC+KyH8FaCWI= -go.opentelemetry.io/otel/sdk v1.23.1 h1:O7JmZw0h76if63LQdsBMKQDWNb5oEcOThG9IrxscV+E= -go.opentelemetry.io/otel/sdk v1.23.1/go.mod h1:LzdEVR5am1uKOOwfBWFef2DCi1nu3SA8XQxx2IerWFk= -go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6HmYY8= -go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -402,11 +416,11 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= +golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= -golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -573,6 +587,7 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= diff --git a/metrics/merger.go b/metrics/merger.go index 1e87634e..48c0c542 100644 --- a/metrics/merger.go +++ b/metrics/merger.go @@ -160,7 +160,8 @@ func (m *Merger) MergeMetrics(pluginMetrics map[string][]byte) *gerr.GatewayDErr // TODO: There should be a better, more efficient way to merge metrics from plugins. var metricsOutput bytes.Buffer - enc := expfmt.NewEncoder(io.Writer(&metricsOutput), expfmt.FmtText) + enc := expfmt.NewEncoder(io.Writer(&metricsOutput), + expfmt.NewFormat(expfmt.FormatType(expfmt.TypeTextPlain))) for pluginName, metrics := range pluginMetrics { // Skip empty metrics. if metrics == nil { diff --git a/network/proxy.go b/network/proxy.go index 3af93ca7..0a3f500d 100644 --- a/network/proxy.go +++ b/network/proxy.go @@ -6,9 +6,12 @@ import ( "errors" "io" "net" + "slices" "time" + sdkAct "github.com/gatewayd-io/gatewayd-plugin-sdk/act" v1 "github.com/gatewayd-io/gatewayd-plugin-sdk/plugin/v1" + "github.com/gatewayd-io/gatewayd/act" "github.com/gatewayd-io/gatewayd/config" gerr "github.com/gatewayd-io/gatewayd/errors" "github.com/gatewayd-io/gatewayd/metrics" @@ -17,7 +20,9 @@ import ( "github.com/getsentry/sentry-go" "github.com/go-co-op/gocron" "github.com/rs/zerolog" + "github.com/spf13/cast" "go.opentelemetry.io/otel" + "golang.org/x/exp/maps" ) type IProxy interface { @@ -385,7 +390,19 @@ func (pr *Proxy) PassThroughToServer(conn *ConnWrapper, stack *Stack) *gerr.Gate stack.Push(&Request{Data: request}) // If the hook wants to terminate the connection, do it. - if pr.shouldTerminate(result) { + if terminate, resp := pr.shouldTerminate(result); terminate { + if resp != nil { + pr.logger.Trace().Fields( + map[string]interface{}{ + "function": "proxy.passthrough", + "result": resp, + }, + ).Msg("Terminating connection with a result from the action") + + // If the terminate action returned a result, use it. + result = resp + } + if modResponse, modReceived := pr.getPluginModifiedResponse(result); modResponse != nil { metrics.ProxyPassThroughsToClient.Inc() metrics.ProxyPassThroughTerminations.Inc() @@ -821,20 +838,50 @@ func (pr *Proxy) sendTrafficToClient( } // shouldTerminate is a function that retrieves the terminate field from the hook result. -// Only the OnTrafficFromClient hook will terminate the connection. -func (pr *Proxy) shouldTerminate(result map[string]interface{}) bool { +// Only the OnTrafficFromClient hook will terminate the request. +func (pr *Proxy) shouldTerminate(result map[string]interface{}) (bool, map[string]interface{}) { _, span := otel.Tracer(config.TracerName).Start(pr.ctx, "shouldTerminate") defer span.End() - // If the hook wants to terminate the connection, do it. - if result != nil { - if terminate, ok := result["terminate"].(bool); ok && terminate { - pr.logger.Debug().Str("function", "proxy.passthrough").Msg("Terminating connection") - return true + if result == nil { + return false, result + } + + outputs, ok := result[sdkAct.Outputs].([]*sdkAct.Output) + if !ok { + pr.logger.Error().Msg("Failed to cast the outputs to the []*act.Output type") + return false, result + } + + // This is a shortcut to avoid running the actions' functions. + // The Terminal field is only present if the action wants to terminate the request, + // that is the `__terminal__` field is set in one of the outputs. + keys := maps.Keys(result) + if slices.Contains(keys, sdkAct.Terminal) { + var actionResult map[string]interface{} + for _, output := range outputs { + actRes, err := pr.pluginRegistry.ActRegistry().Run( + output, act.WithResult(result)) + // If the action is async and we received a sentinel error, + // don't log the error. + if err != nil && !errors.Is(err, gerr.ErrAsyncAction) { + pr.logger.Error().Err(err).Msg("Error running policy") + } + // The terminate action should return a map. + if v, ok := actRes.(map[string]interface{}); ok { + actionResult = v + } } + pr.logger.Debug().Fields( + map[string]interface{}{ + "function": "proxy.passthrough", + "reason": "terminate", + }, + ).Msg("Terminating request") + return cast.ToBool(result[sdkAct.Terminal]), actionResult } - return false + return false, result } // getPluginModifiedRequest is a function that retrieves the modified request diff --git a/network/proxy_test.go b/network/proxy_test.go index 717c0b19..e340fae2 100644 --- a/network/proxy_test.go +++ b/network/proxy_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/gatewayd-io/gatewayd/act" "github.com/gatewayd-io/gatewayd/config" "github.com/gatewayd-io/gatewayd/logging" "github.com/gatewayd-io/gatewayd/plugin" @@ -43,14 +44,19 @@ func TestNewProxy(t *testing.T) { err := newPool.Put(client.ID, client) assert.Nil(t, err) + // Create a new act registry + actRegistry := act.NewActRegistry( + act.BuiltinSignals(), act.BuiltinPolicies(), act.BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + // Create a proxy with a fixed buffer newPool proxy := NewProxy( context.Background(), newPool, plugin.NewRegistry( context.Background(), + actRegistry, config.Loose, - config.Stop, logger, false, ), @@ -84,6 +90,11 @@ func BenchmarkNewProxy(b *testing.B) { // Create a connection newPool newPool := pool.NewPool(context.Background(), config.EmptyPoolCapacity) + // Create a new act registry + actRegistry := act.NewActRegistry( + act.BuiltinSignals(), act.BuiltinPolicies(), act.BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + // Create a proxy with a fixed buffer newPool for i := 0; i < b.N; i++ { proxy := NewProxy( @@ -91,8 +102,8 @@ func BenchmarkNewProxy(b *testing.B) { newPool, plugin.NewRegistry( context.Background(), + actRegistry, config.Loose, - config.Stop, logger, false, ), @@ -128,14 +139,19 @@ func BenchmarkProxyConnectDisconnect(b *testing.B) { } newPool.Put("client", NewClient(context.Background(), &clientConfig, logger, nil)) //nolint:errcheck + // Create a new act registry + actRegistry := act.NewActRegistry( + act.BuiltinSignals(), act.BuiltinPolicies(), act.BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + // Create a proxy with a fixed buffer newPool proxy := NewProxy( context.Background(), newPool, plugin.NewRegistry( context.Background(), + actRegistry, config.Loose, - config.Stop, logger, false, ), @@ -178,14 +194,19 @@ func BenchmarkProxyPassThrough(b *testing.B) { } newPool.Put("client", NewClient(context.Background(), &clientConfig, logger, nil)) //nolint:errcheck + // Create a new act registry + actRegistry := act.NewActRegistry( + act.BuiltinSignals(), act.BuiltinPolicies(), act.BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + // Create a proxy with a fixed buffer newPool proxy := NewProxy( context.Background(), newPool, plugin.NewRegistry( context.Background(), + actRegistry, config.Loose, - config.Stop, logger, false, ), @@ -233,14 +254,19 @@ func BenchmarkProxyIsHealthyAndIsExhausted(b *testing.B) { client := NewClient(context.Background(), &clientConfig, logger, nil) newPool.Put("client", client) //nolint:errcheck + // Create a new act registry + actRegistry := act.NewActRegistry( + act.BuiltinSignals(), act.BuiltinPolicies(), act.BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + // Create a proxy with a fixed buffer newPool proxy := NewProxy( context.Background(), newPool, plugin.NewRegistry( context.Background(), + actRegistry, config.Loose, - config.Stop, logger, false, ), @@ -286,14 +312,19 @@ func BenchmarkProxyAvailableAndBusyConnections(b *testing.B) { client := NewClient(context.Background(), &clientConfig, logger, nil) newPool.Put("client", client) //nolint:errcheck + // Create a new act registry + actRegistry := act.NewActRegistry( + act.BuiltinSignals(), act.BuiltinPolicies(), act.BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) + // Create a proxy with a fixed buffer newPool proxy := NewProxy( context.Background(), newPool, plugin.NewRegistry( context.Background(), + actRegistry, config.Loose, - config.Stop, logger, false, ), diff --git a/network/server_test.go b/network/server_test.go index c2cf1205..95658778 100644 --- a/network/server_test.go +++ b/network/server_test.go @@ -11,6 +11,7 @@ import ( "time" v1 "github.com/gatewayd-io/gatewayd-plugin-sdk/plugin/v1" + "github.com/gatewayd-io/gatewayd/act" "github.com/gatewayd-io/gatewayd/config" "github.com/gatewayd-io/gatewayd/logging" "github.com/gatewayd-io/gatewayd/plugin" @@ -38,10 +39,13 @@ func TestRunServer(t *testing.T) { FileName: "server_test.log", }) + actRegistry := act.NewActRegistry( + act.BuiltinSignals(), act.BuiltinPolicies(), act.BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) pluginRegistry := plugin.NewRegistry( context.Background(), + actRegistry, config.Loose, - config.Stop, logger, false, ) @@ -51,6 +55,13 @@ func TestRunServer(t *testing.T) { pluginRegistry.AddHook(v1.HookName_HOOK_NAME_ON_TRAFFIC_FROM_SERVER, 1, onOutgoingTraffic) pluginRegistry.AddHook(v1.HookName_HOOK_NAME_ON_TRAFFIC_TO_CLIENT, 1, onOutgoingTraffic) + assert.NotNil(t, pluginRegistry.ActRegistry()) + assert.NotNil(t, pluginRegistry.ActRegistry().Signals) + assert.NotNil(t, pluginRegistry.ActRegistry().Policies) + assert.NotNil(t, pluginRegistry.ActRegistry().Actions) + assert.Equal(t, config.DefaultPolicy, pluginRegistry.ActRegistry().DefaultPolicy.Name) + assert.Equal(t, config.DefaultPolicy, pluginRegistry.ActRegistry().DefaultSignal.Name) + clientConfig := config.Client{ Network: "tcp", Address: "localhost:5432", diff --git a/plugin/plugin_registry.go b/plugin/plugin_registry.go index 7171d424..da1247eb 100644 --- a/plugin/plugin_registry.go +++ b/plugin/plugin_registry.go @@ -8,8 +8,10 @@ import ( "time" "github.com/Masterminds/semver/v3" + sdkAct "github.com/gatewayd-io/gatewayd-plugin-sdk/act" sdkPlugin "github.com/gatewayd-io/gatewayd-plugin-sdk/plugin" v1 "github.com/gatewayd-io/gatewayd-plugin-sdk/plugin/v1" + "github.com/gatewayd-io/gatewayd/act" "github.com/gatewayd-io/gatewayd/config" gerr "github.com/gatewayd-io/gatewayd/errors" "github.com/gatewayd-io/gatewayd/logging" @@ -28,10 +30,10 @@ type IHook interface { Hooks() map[v1.HookName]map[sdkPlugin.Priority]sdkPlugin.Method Run( ctx context.Context, - args map[string]interface{}, + args map[string]any, hookName v1.HookName, opts ...grpc.CallOption, - ) (map[string]interface{}, *gerr.GatewayDError) + ) (map[string]any, *gerr.GatewayDError) } //nolint:interfacebloat @@ -47,20 +49,22 @@ type IRegistry interface { Shutdown() LoadPlugins(ctx context.Context, plugins []config.Plugin, startTimeout time.Duration) RegisterHooks(ctx context.Context, pluginID sdkPlugin.Identifier) + Apply(hookName string, result *v1.Struct) ([]*sdkAct.Output, bool) + ActRegistry() *act.Registry // Hook management IHook } type Registry struct { - plugins pool.IPool - hooks map[v1.HookName]map[sdkPlugin.Priority]sdkPlugin.Method - ctx context.Context //nolint:containedctx - devMode bool + plugins pool.IPool + actRegistry *act.Registry + hooks map[v1.HookName]map[sdkPlugin.Priority]sdkPlugin.Method + ctx context.Context //nolint:containedctx + devMode bool Logger zerolog.Logger Compatibility config.CompatibilityPolicy - Termination config.TerminationPolicy StartTimeout time.Duration } @@ -69,8 +73,8 @@ var _ IRegistry = (*Registry)(nil) // NewRegistry creates a new plugin registry. func NewRegistry( ctx context.Context, + actRegistry *act.Registry, compatibility config.CompatibilityPolicy, - termination config.TerminationPolicy, logger zerolog.Logger, devMode bool, ) *Registry { @@ -79,12 +83,12 @@ func NewRegistry( return &Registry{ plugins: pool.NewPool(regCtx, config.EmptyPoolCapacity), + actRegistry: actRegistry, hooks: map[v1.HookName]map[sdkPlugin.Priority]sdkPlugin.Method{}, ctx: regCtx, devMode: devMode, Logger: logger, Compatibility: compatibility, - Termination: termination, } } @@ -236,7 +240,7 @@ func (reg *Registry) AddHook(hookName v1.HookName, priority sdkPlugin.Priority, } else { if _, ok := reg.hooks[hookName][priority]; ok { reg.Logger.Warn().Fields( - map[string]interface{}{ + map[string]any{ "hookName": hookName.String(), "priority": priority, }, @@ -254,10 +258,10 @@ func (reg *Registry) AddHook(hookName v1.HookName, priority sdkPlugin.Priority, // The opts are passed to the hooks as well to allow them to use the grpc.CallOption. func (reg *Registry) Run( ctx context.Context, - args map[string]interface{}, + args map[string]any, hookName v1.HookName, opts ...grpc.CallOption, -) (map[string]interface{}, *gerr.GatewayDError) { +) (map[string]any, *gerr.GatewayDError) { _, span := otel.Tracer(config.TracerName).Start(reg.ctx, "Run") defer span.End() @@ -272,7 +276,7 @@ func (reg *Registry) Run( defer cancel() // Cast custom fields to their primitive types, like time.Duration to float64. - args = CastToPrimitiveTypes(args) + args = castToPrimitiveTypes(args) // Create v1.Struct from args. var params *v1.Struct @@ -296,6 +300,7 @@ func (reg *Registry) Run( // Run hooks, passing the result of the previous hook to the next one. returnVal := &v1.Struct{} + outputs := []*sdkAct.Output{} // The signature of parameters and args MUST be the same for this to work. for idx, priority := range priorities { var result *v1.Struct @@ -308,7 +313,7 @@ func (reg *Registry) Run( if err != nil { reg.Logger.Error().Err(err).Fields( - map[string]interface{}{ + map[string]any{ "hookName": hookName.String(), "priority": priority, }, @@ -316,21 +321,69 @@ func (reg *Registry) Run( span.RecordError(err) } - // Update the last return value with the current result + if result == nil { + // Remove the hook from the registry, log the error and execute the next hook. + reg.Logger.Error().Fields( + map[string]any{ + "hookName": hookName.String(), + "priority": priority, + }, + ).Msg("Hook returned nil result, so it won't work properly") + delete(reg.hooks[hookName], priority) + continue + } + + out, terminal := reg.Apply(hookName.String(), result) + outputs = append(outputs, out...) + + if terminal { + // Any signal matching a policy with a terminal action + // will terminate the execution of the rest of the hooks. + reg.Logger.Debug().Msg("Terminal signal received") + span.AddEvent("Terminal signal received") + resultMap := result.AsMap() + resultMap[sdkAct.Outputs] = outputs + resultMap[sdkAct.Terminal] = true + return resultMap, nil + } + returnVal = result + } - // If the termination policy is set to Stop, check if the terminate flag - // is set to true. If it is, abort the execution of the rest of the registered hooks. - if reg.Termination == config.Stop { - // If the terminate flag is set to true, - // abort the execution of the rest of the registered hooks. - if terminate, ok := result.GetFields()["terminate"]; ok && terminate.GetBoolValue() { - break - } + returnMap := returnVal.AsMap() + returnMap[sdkAct.Outputs] = outputs + return returnMap, nil +} + +// Apply applies policies to the result. +func (reg *Registry) Apply(hookName string, result *v1.Struct) ([]*sdkAct.Output, bool) { + _, span := otel.Tracer(config.TracerName).Start(reg.ctx, "Apply") + defer span.End() + + // Get signals from the result. + signals := getSignals(result.AsMap()) + // Apply policies to the signals. + // The outputs contains the verdicts of the policies and their metadata. + // And using this list, the caller can take further actions. + outputs := applyPolicies(hookName, signals, reg.Logger, reg.ActRegistry()) + + // If no policies are found, return a default output. + // Note: this should never happen, as the default policy is always loaded. + if len(outputs) == 0 { + reg.Logger.Debug().Msg("No policies found for the given signals") + return nil, false + } + + // Check if any of the policies have a terminal action. + var terminal bool + for _, output := range outputs { + if output.Verdict != nil && output.Terminal { + terminal = true + break } } - return returnVal.AsMap(), nil + return outputs, terminal } // LoadPlugins loads plugins from the config file. @@ -499,7 +552,7 @@ func (reg *Registry) LoadPlugins( for _, req := range plugin.Requires { if !reg.Exists(req.Name, req.Version, req.RemoteURL) { reg.Logger.Debug().Fields( - map[string]interface{}{ + map[string]any{ "name": plugin.ID.Name, "requirement": req.Name, }, @@ -511,7 +564,7 @@ func (reg *Registry) LoadPlugins( continue } reg.Logger.Debug().Fields( - map[string]interface{}{ + map[string]any{ "name": plugin.ID.Name, "requirement": req.Name, }, @@ -666,7 +719,7 @@ func (reg *Registry) RegisterHooks(ctx context.Context, pluginID sdkPlugin.Ident continue } - reg.Logger.Debug().Fields(map[string]interface{}{ + reg.Logger.Debug().Fields(map[string]any{ "hook": hookName.String(), "priority": pluginImpl.Priority, "name": pluginImpl.ID.Name, @@ -675,3 +728,10 @@ func (reg *Registry) RegisterHooks(ctx context.Context, pluginID sdkPlugin.Ident reg.AddHook(hookName, pluginImpl.Priority, hookMethod) } } + +// ActRegistry returns the act registry. +func (reg *Registry) ActRegistry() *act.Registry { + _, span := otel.Tracer(config.TracerName).Start(reg.ctx, "ActRegistry") + defer span.End() + return reg.actRegistry +} diff --git a/plugin/plugin_registry_test.go b/plugin/plugin_registry_test.go index aa35b39f..ff8b911b 100644 --- a/plugin/plugin_registry_test.go +++ b/plugin/plugin_registry_test.go @@ -7,6 +7,7 @@ import ( sdkPlugin "github.com/gatewayd-io/gatewayd-plugin-sdk/plugin" v1 "github.com/gatewayd-io/gatewayd-plugin-sdk/plugin/v1" + "github.com/gatewayd-io/gatewayd/act" "github.com/gatewayd-io/gatewayd/config" "github.com/gatewayd-io/gatewayd/logging" "github.com/rs/zerolog" @@ -21,14 +22,17 @@ func NewPluginRegistry(t *testing.T) *Registry { Output: []config.LogOutput{config.Console}, TimeFormat: zerolog.TimeFormatUnix, ConsoleTimeFormat: time.RFC3339, - Level: zerolog.DebugLevel, + Level: zerolog.InfoLevel, NoColor: true, } logger := logging.NewLogger(context.Background(), cfg) + actRegistry := act.NewActRegistry( + act.BuiltinSignals(), act.BuiltinPolicies(), act.BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) reg := NewRegistry( context.Background(), + actRegistry, config.Loose, - config.Stop, logger, false, ) @@ -124,10 +128,13 @@ func BenchmarkHookRun(b *testing.B) { NoColor: true, } logger := logging.NewLogger(context.Background(), cfg) + actRegistry := act.NewActRegistry( + act.BuiltinSignals(), act.BuiltinPolicies(), act.BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger) reg := NewRegistry( context.Background(), + actRegistry, config.Loose, - config.Stop, logger, false, ) diff --git a/plugin/utils.go b/plugin/utils.go index bf546ef3..f237bf11 100644 --- a/plugin/utils.go +++ b/plugin/utils.go @@ -3,6 +3,11 @@ package plugin import ( "os/exec" "time" + + sdkAct "github.com/gatewayd-io/gatewayd-plugin-sdk/act" + "github.com/gatewayd-io/gatewayd/act" + "github.com/rs/zerolog" + "github.com/spf13/cast" ) // NewCommand returns a command with the given arguments and environment variables. @@ -14,9 +19,9 @@ func NewCommand(cmd string, args []string, env []string) *exec.Cmd { return command } -// CastToPrimitiveTypes casts the values of a map to its primitive type +// castToPrimitiveTypes casts the values of a map to its primitive type // (e.g. time.Duration to float64) to prevent structpb invalid type(s) errors. -func CastToPrimitiveTypes(args map[string]interface{}) map[string]interface{} { +func castToPrimitiveTypes(args map[string]interface{}) map[string]interface{} { for key, value := range args { switch value := value.(type) { case time.Duration: @@ -24,7 +29,7 @@ func CastToPrimitiveTypes(args map[string]interface{}) map[string]interface{} { args[key] = value.String() case map[string]interface{}: // Recursively cast nested maps. - args[key] = CastToPrimitiveTypes(value) + args[key] = castToPrimitiveTypes(value) case []interface{}: // Recursively cast nested arrays. array := make([]interface{}, len(value)) @@ -45,3 +50,54 @@ func CastToPrimitiveTypes(args map[string]interface{}) map[string]interface{} { } return args } + +// getSignals decodes the signals from the result map and returns them as a list of Signal objects. +func getSignals(result map[string]any) []sdkAct.Signal { + decodedSignals := []sdkAct.Signal{} + + if signals, ok := result[sdkAct.Signals]; ok { + signals := cast.ToSlice(signals) + for _, signal := range signals { + signalMap := cast.ToStringMap(signal) + name := cast.ToString(signalMap[sdkAct.Name]) + metadata := cast.ToStringMap(signalMap[sdkAct.Metadata]) + + if name != "" { + // Add the signal to the list of signals. + decodedSignals = append(decodedSignals, sdkAct.Signal{ + Name: name, + Metadata: metadata, + }) + } + } + } + + return decodedSignals +} + +// applyPolicies applies the policies to the signals and returns the outputs. +func applyPolicies( + hookName string, signals []sdkAct.Signal, logger zerolog.Logger, reg act.IRegistry, +) []*sdkAct.Output { + signalNames := []string{} + for _, signal := range signals { + signalNames = append(signalNames, signal.Name) + } + + logger.Debug().Fields( + map[string]interface{}{ + "hook": hookName, + "signals": signalNames, + }, + ).Msg("Detected signals from the plugin hook") + + outputs := reg.Apply(signals) + logger.Debug().Fields( + map[string]interface{}{ + "hook": hookName, + "outputs": outputs, + }, + ).Msg("Applied policies to signals") + + return outputs +} diff --git a/plugin/utils_test.go b/plugin/utils_test.go index 80d83c60..e123c8ad 100644 --- a/plugin/utils_test.go +++ b/plugin/utils_test.go @@ -4,6 +4,11 @@ import ( "testing" "time" + sdkAct "github.com/gatewayd-io/gatewayd-plugin-sdk/act" + "github.com/gatewayd-io/gatewayd/act" + "github.com/gatewayd-io/gatewayd/config" + "github.com/rs/zerolog" + "github.com/spf13/cast" "github.com/stretchr/testify/assert" ) @@ -16,8 +21,8 @@ func Test_NewCommand(t *testing.T) { assert.Equal(t, []string{"test=123"}, cmd.Env) } -// Test_CastToPrimitiveTypes tests the CastToPrimitiveTypes function. -func Test_CastToPrimitiveTypes(t *testing.T) { +// Test_castToPrimitiveTypes tests the CastToPrimitiveTypes function. +func Test_castToPrimitiveTypes(t *testing.T) { actual := map[string]interface{}{ "string": "test", "int": 123, @@ -51,6 +56,55 @@ func Test_CastToPrimitiveTypes(t *testing.T) { }, } - casted := CastToPrimitiveTypes(actual) + casted := castToPrimitiveTypes(actual) assert.Equal(t, expected, casted) } + +// Test_getSignals tests the getSignals function. +func Test_getSignals(t *testing.T) { + result := map[string]interface{}{ + sdkAct.Signals: []any{ + (&sdkAct.Signal{ + Name: "test", + Metadata: map[string]any{"test": "test"}, + }).ToMap(), + sdkAct.Passthrough().ToMap(), + }, + } + signals := getSignals(result) + assert.Len(t, signals, 2) + assert.Equal(t, "test", signals[0].Name) + assert.Equal(t, "test", signals[0].Metadata["test"]) + assert.Equal(t, "passthrough", signals[1].Name) + assert.Nil(t, signals[1].Metadata) +} + +// Test_getSignals_empty tests the getSignals function with an empty result. +func Test_getSignals_empty(t *testing.T) { + result := map[string]interface{}{} + signals := getSignals(result) + assert.Len(t, signals, 0) +} + +// Test_applyPolicies tests the applyPolicies function with a passthrough policy. +// It also tests the Run function of the registered passthrough (built-in) action. +func Test_applyPolicies(t *testing.T) { + logger := zerolog.Logger{} + actRegistry := act.NewActRegistry( + act.BuiltinSignals(), act.BuiltinPolicies(), act.BuiltinActions(), + config.DefaultPolicy, config.DefaultPolicyTimeout, logger, + ) + + output := applyPolicies( + "onTrafficFromClient", []sdkAct.Signal{*sdkAct.Passthrough()}, logger, actRegistry) + assert.Len(t, output, 1) + assert.Equal(t, "passthrough", output[0].MatchedPolicy) + assert.Nil(t, output[0].Metadata) + assert.True(t, output[0].Sync) + assert.False(t, output[0].Terminal) + assert.True(t, cast.ToBool(output[0].Verdict)) + + result, gerr := actRegistry.Run(output[0], act.WithLogger(logger)) + assert.Nil(t, gerr) + assert.True(t, cast.ToBool(result)) +}