Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Traffic Policy revisions #156

Merged
merged 3 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## 1.8.1
Enhancements:

- Provides access to structs for building a Traffic Policy configuration

Breaking changes:

- Renames pre-release option `WithPolicyConfig` to `WithPolicyString`
- Changes signature of `WithPolicy` option to accept the newly exposed structs for building a Traffic Policy configuration

## 1.8.0
- Adds the `WithPolicy` and `WithPolicyConfig` options for applying a Traffic Policy to an endpoint.

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.8.0
1.8.1
201 changes: 26 additions & 175 deletions config/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,69 +2,24 @@ package config

import (
"encoding/json"
"errors"
"fmt"

"golang.ngrok.com/ngrok/internal/pb"
po "golang.ngrok.com/ngrok/policy"
)

type policy struct {
Inbound inboundRules `json:"inbound,omitempty"`
Outbound outboundRules `json:"outbound,omitempty"`
}

type inboundRules []policyRule
type outboundRules []policyRule

type policyRule struct {
Name string `json:"name,omitempty"`
Expressions []string `json:"expressions,omitempty"`
Actions []action `json:"actions"`
}
type action struct {
Type string `json:"type"`
Config json.RawMessage `json:"config,omitempty"`
}

type PolicyActionOption = option[*action]
type PolicyRuleOption = option[*policyRule]
type PolicyRuleSetOption = option[*[]policyRule]
type PolicyOption = option[*policy]

// Supports conversion to a json string
type JsonConvertible interface {
ToJSON() string
}

func (p *policy) ToJSON() string {
return toJSON(p)
}

func (p policyRule) ToJSON() string {
return toJSON(p)
}

func (p action) ToJSON() string {
return toJSON(p)
}

func toJSON(o any) string {
bytes, err := json.Marshal(o)
type policy po.Policy
type rule po.Rule
type action po.Action

if err != nil {
panic(fmt.Sprintf("failed to convert to json with error: %s", err.Error()))
}

return string(bytes)
}

// WithPolicyConfig configures this edge with the provided policy configuration
// passed as a json string and overwrites any prevously-set traffic policy
// WithPolicyString configures this edge with the provided policy configuration
// passed as a json string and overwrites any previously-set traffic policy
// https://ngrok.com/docs/http/traffic-policy
func WithPolicyConfig(jsonStr string) interface {
func WithPolicyString(jsonStr string) interface {
HTTPEndpointOption
TLSEndpointOption
TCPEndpointOption
JsonConvertible
} {
p := &policy{}
if err := json.Unmarshal([]byte(jsonStr), p); err != nil {
Expand All @@ -74,130 +29,17 @@ func WithPolicyConfig(jsonStr string) interface {
return p
}

// WithPolicy configures this edge with the given traffic and overwrites any
// WithPolicy configures this edge with the given traffic policy and overwrites any
// previously-set traffic policy
// https://ngrok.com/docs/http/traffic-policy/
func WithPolicy(opts ...PolicyOption) interface {
func WithPolicy(p po.Policy) interface {
HTTPEndpointOption
TLSEndpointOption
TCPEndpointOption
JsonConvertible
} {
cfg := &policy{}
applyOpts(cfg, opts...)

return cfg
}

// WithInboundRules adds the provided policy rules to the inbound
// set of the given policy.
// The order in which policies are specified is observed.
func WithInboundRules(opts ...PolicyRuleSetOption) PolicyOption {
rules := []policyRule{}
applyOpts(&rules, opts...)

return inboundRules(rules)
}

// WithOutboundRules adds the provided policy to be outbound
// set of the given policy.
// The order in which policies are specified is observed.
func WithOutboundRules(opts ...PolicyRuleSetOption) PolicyOption {
rules := []policyRule{}
applyOpts(&rules, opts...)

return outboundRules(rules)
}

// WithPolicyRule provides a policy rule built from the given options.
func WithPolicyRule(opts ...PolicyRuleOption) interface {
PolicyRuleSetOption
JsonConvertible
} {
pr := policyRule{}
applyOpts(&pr, opts...)
ret := policy(p)

return pr
}

// WithPolicyName sets the provided name on a policy rule.
func WithPolicyName(n string) PolicyRuleOption {
return optionFunc[*policyRule](
func(r *policyRule) {
r.Name = n
})
}

// WithPolicyExpression appends the provided CEL statement to a policy rule's expressions.
func WithPolicyExpression(e string) PolicyRuleOption {
return optionFunc[*policyRule](
func(r *policyRule) {
r.Expressions = append(r.Expressions, e)
})
}

// WithPolicyAction appends the provided action to the set of the policy rule.
// The order the actions are specified is observed.
func WithPolicyAction(opts ...PolicyActionOption) interface {
PolicyRuleOption
JsonConvertible
} {
a := action{}
applyOpts(&a, opts...)

return a
}

// WithActionType sets the provided type for this action. Type must be specified.
func WithPolicyActionType(t string) PolicyActionOption {
return optionFunc[*action](func(a *action) { a.Type = t })
}

// WithConfig sets the provided json string as the configuration for this action
func WithPolicyActionConfig(cfg string) PolicyActionOption {
return optionFunc[*action](
func(a *action) {
a.Config = []byte(cfg)
})
}

// supports inbound rules as an a policy option
func (ib inboundRules) apply(p *policy) {
p.Inbound = append(p.Inbound, ib...)
}

// supports outbound rules as a policy option
func (ib outboundRules) apply(p *policy) {
p.Outbound = append(p.Outbound, ib...)
}

// supports policy rule as an option of a collection of
// rules, which can be used for inbound or outbound
func (pr policyRule) apply(r *[]policyRule) {
*r = append(*r, pr)
}

// supports action as an option of a policy rule
func (a action) apply(p *policyRule) {
p.Actions = append(p.Actions, a)
}

// an option that is applicable to the specified type
type option[T any] interface {
apply(T)
}

type optionFunc[T any] func(T)

func (f optionFunc[T]) apply(a T) {
f(a)
}

// applies the set of options to the specified target
func applyOpts[T any](target T, opts ...option[T]) {
for _, o := range opts {
o.apply(target)
}
return &ret
}

func (p *policy) ApplyTLS(opts *tlsOptions) {
Expand All @@ -218,31 +60,40 @@ func (p *policy) toProtoConfig() *pb.MiddlewareConfiguration_Policy {
}
inbound := make([]*pb.MiddlewareConfiguration_PolicyRule, len(p.Inbound))
for i, inP := range p.Inbound {
inbound[i] = inP.toProtoConfig()
inbound[i] = rule(inP).toProtoConfig()
}

outbound := make([]*pb.MiddlewareConfiguration_PolicyRule, len(p.Outbound))
for i, outP := range p.Outbound {
outbound[i] = outP.toProtoConfig()
outbound[i] = rule(outP).toProtoConfig()
}
return &pb.MiddlewareConfiguration_Policy{
Inbound: inbound,
Outbound: outbound,
}
}

func (pr policyRule) toProtoConfig() *pb.MiddlewareConfiguration_PolicyRule {
func (pr rule) toProtoConfig() *pb.MiddlewareConfiguration_PolicyRule {
actions := make([]*pb.MiddlewareConfiguration_PolicyAction, len(pr.Actions))
for i, act := range pr.Actions {
actions[i] = act.toProtoConfig()
actions[i] = action(act).toProtoConfig()
}

return &pb.MiddlewareConfiguration_PolicyRule{Name: pr.Name, Expressions: pr.Expressions, Actions: actions}
}

func (a action) toProtoConfig() *pb.MiddlewareConfiguration_PolicyAction {
var cfgBytes []byte = nil
if len(a.Config) > 0 {
var err error
cfgBytes, err = json.Marshal(a.Config)

if err != nil {
panic(errors.New(fmt.Sprintf("failed to parse action configuration due to error: %s", err.Error())))
}
}
return &pb.MiddlewareConfiguration_PolicyAction{
Type: a.Type,
Config: []byte(a.Config),
Config: cfgBytes,
}
}
Loading
Loading