From 6ccf9b70be368fb935dbc133bf547eae9f590630 Mon Sep 17 00:00:00 2001 From: Mostafa Moradian Date: Sat, 2 Mar 2024 00:05:04 +0100 Subject: [PATCH] Act system (#451) This giant PR adds the very first version of the Act system that was proposed in [this proposal](https://github.com/gatewayd-io/proposals/issues/5). The old way of signaling was static and only supported a single signal: `terminate`. The new system support more signals, adds proper policies that can be easily controlled by the users and the actions are executed in sync and async mode. The Act system consists of these components: 1. **Act Registry**: takes care of registering signals, policies and actions. It also applies policies to signals to produce outputs for actions and runs actions using those outputs. 2. **Signals**: plugins' hooks can return signal(s) as part of their request/response. These signals tell GatewayD what to do. 3. **Policies**: signals pass through predefined policies that will decide whether GatewayD should react to the signal or not. 4. **Actions**: actions run in sync or async mode and perform a function. Sync actions are used to control traffic (passthrough, terminate, etc.) and other parts of the system, and async actions can other things (log, publish a message to Kafka, etc.). 5. **Plugin Registry**: after running a hook on each plugin, the signals are extracted and the policies are applied to those signals. The output of those policy evaluations are returned to the caller, which knows how to run action and use its results. And the code spans over two projects: 1. **GatewayD**: all the above components of the Act system are in GatewayD. 2. **SDK**: types and helper functions for creating and exporting signals are in the SDK. ### Breaking changes The old way of terminating requests don't work anymore, as it was refactored in #442 and all the plugins are updated to pick up the changes. --- .github/workflows/release.yaml | 6 +- .github/workflows/test.yaml | 19 +- .gitignore | 1 + .golangci.yaml | 3 + Dockerfile | 2 +- Makefile | 2 +- act/builtins.go | 162 ++++++++++++ act/registry.go | 285 ++++++++++++++++++++ act/registry_test.go | 466 +++++++++++++++++++++++++++++++++ api/api.go | 20 +- api/api_test.go | 17 +- api/v1/api.pb.go | 328 ++++++++++++----------- api/v1/api.proto | 2 +- api/v1/api.swagger.json | 1 - cmd/run.go | 36 ++- config/config.go | 4 +- config/constants.go | 13 +- config/getters.go | 4 - config/types.go | 10 +- errors/errors.go | 35 ++- gatewayd_plugins.yaml | 17 +- go.mod | 29 +- go.sum | 59 +++-- metrics/merger.go | 3 +- network/proxy.go | 65 ++++- network/proxy_test.go | 43 ++- network/server_test.go | 13 +- plugin/plugin_registry.go | 114 ++++++-- plugin/plugin_registry_test.go | 13 +- plugin/utils.go | 62 ++++- plugin/utils_test.go | 60 ++++- 31 files changed, 1585 insertions(+), 309 deletions(-) create mode 100644 act/builtins.go create mode 100644 act/registry.go create mode 100644 act/registry_test.go 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)) +}