Skip to content

Commit

Permalink
feat!: Version schema for rule sets (#436)
Browse files Browse the repository at this point in the history
  • Loading branch information
dadrus authored Apr 13, 2023
1 parent df98903 commit dba0a87
Show file tree
Hide file tree
Showing 132 changed files with 6,368 additions and 4,178 deletions.
2 changes: 1 addition & 1 deletion .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ ignore:
- "**/*generated.go"
- "**/mocks"
- "**/*_test.go"
- "**/testsupport/*.go"
- "**/testsupport/**"
- "**/zz_generated.*"
coverage:
status:
Expand Down
2 changes: 2 additions & 0 deletions .mockery.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
case: underscore
with-expecter: true
5 changes: 4 additions & 1 deletion cmd/serve/decision.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package serve

import (
"os"

"github.com/spf13/cobra"
"go.uber.org/fx"

Expand All @@ -36,7 +38,8 @@ func NewDecisionCommand() *cobra.Command {
app, err := createDecisionApp(cmd)
if err != nil {
cmd.PrintErrf("Failed to initialize decision service: %v", err)
panic(err)

os.Exit(1)
}

app.Run()
Expand Down
5 changes: 4 additions & 1 deletion cmd/serve/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package serve

import (
"os"

"github.com/spf13/cobra"
"go.uber.org/fx"

Expand All @@ -35,7 +37,8 @@ func NewProxyCommand() *cobra.Command {
app, err := createProxyApp(cmd)
if err != nil {
cmd.PrintErrf("Failed to initialize proxy service: %v", err)
panic(err)

os.Exit(1)
}

app.Run()
Expand Down
33 changes: 18 additions & 15 deletions cmd/validate/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,36 @@
package validate

import (
"errors"
"os"

"github.com/spf13/cobra"

"github.com/dadrus/heimdall/internal/config"
)

var ErrNoConfigFile = errors.New("no config file provided")

// NewValidateConfigCommand represents the "validate config" command.
func NewValidateConfigCommand() *cobra.Command {
return &cobra.Command{
Use: "config",
Short: "Validates heimdall's configuration",
RunE: func(cmd *cobra.Command, args []string) error {
configPath, _ := cmd.Flags().GetString("config")
if len(configPath) == 0 {
return ErrNoConfigFile
}

if err := config.ValidateConfig(configPath); err != nil {
Use: "config",
Short: "Validates heimdall's configuration",
Example: "heimdall validate config -c myconfig.yaml",
Run: func(cmd *cobra.Command, args []string) {
if err := validateConfig(cmd); err != nil {
cmd.PrintErrf("%v\n", err)
} else {
cmd.Printf("Configuration is valid\n")

os.Exit(1)
}

return nil
cmd.Println("Configuration is valid")
},
}
}

func validateConfig(cmd *cobra.Command) error {
configPath, _ := cmd.Flags().GetString("config")
if len(configPath) == 0 {
return ErrNoConfigFile
}

return config.ValidateConfig(configPath)
}
92 changes: 92 additions & 0 deletions cmd/validate/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package validate

import (
"bytes"
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/dadrus/heimdall/internal/x/testsupport"
)

func TestValidateConfig(t *testing.T) {
t.Parallel()

for _, tc := range []struct {
uc string
confFile string
expError error
}{
{uc: "no config provided", expError: ErrNoConfigFile},
{uc: "invalid config", confFile: "doesnotexist.yaml", expError: os.ErrNotExist},
{uc: "valid config", confFile: "../../internal/config/test_data/test_config.yaml"},
} {
t.Run(tc.uc, func(t *testing.T) {
// GIVEN
cmd := NewValidateConfigCommand()
cmd.Flags().StringP("config", "c", "", "Path to heimdall's configuration file.")

if len(tc.confFile) != 0 {
err := cmd.ParseFlags([]string{"--config", tc.confFile})
require.NoError(t, err)
}

// WHEN
err := validateConfig(cmd)

// THEN
if tc.expError != nil {
require.Error(t, err)
assert.ErrorIs(t, err, tc.expError)
} else {
require.NoError(t, err)
}
})
}
}

func TestRunValidateCommand(t *testing.T) {
t.Parallel()

for _, tc := range []struct {
uc string
confFile string
expError string
}{
{uc: "invalid config", confFile: "doesnotexist.yaml", expError: "no such file or dir"},
{uc: "valid config", confFile: "../../internal/config/test_data/test_config.yaml"},
} {
t.Run(tc.uc, func(t *testing.T) {
// GIVEN
exit, err := testsupport.PatchOSExit(t, func(int) {})
require.NoError(t, err)

cmd := NewValidateConfigCommand()

buf := bytes.NewBuffer([]byte{})
cmd.SetOut(buf)
cmd.SetErr(buf)

cmd.Flags().StringP("config", "c", "", "Path to heimdall's configuration file.")

if len(tc.confFile) != 0 {
err := cmd.ParseFlags([]string{"--config", tc.confFile})
require.NoError(t, err)
}

// WHEN
cmd.Run(cmd, []string{})

log := buf.String()
if len(tc.expError) != 0 {
assert.Contains(t, log, tc.expError)
assert.True(t, exit.Called)
assert.Equal(t, 1, exit.Code)
} else {
assert.Contains(t, log, "Configuration is valid")
}
})
}
}
5 changes: 5 additions & 0 deletions cmd/validate/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package validate

import "errors"

var ErrNoConfigFile = errors.New("no config file provided")
17 changes: 16 additions & 1 deletion docs/content/docs/configuration/rules/configuration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,27 @@ This example uses two error handlers, named `foo` and `bar`. `bar` will only be

== Rule Set

A rule set is just a list of rules, typically defined in a format specified by a particular link:{{< relref "providers.adoc" >}}[provider]. In its simplest case a rule set does not require further configuration options and can look as shown below:
In principle, a rule set is just a list of rules with some additional meta information. Each `RuleSet` definition has the following attributes if not stated otherwise by a particular link:{{< relref "providers.adoc" >}}[provider]:

* *`version`*: _string_ (mandatory)
+
The version schema of the `RuleSet`. The current version of heimdall supports only the version `1`.

* *`name`*: _string_ (optional)
+
The name of a rule set. Used only for logging purposes.

* *`rules`*: _link:{{< relref "configuration.adoc#_rule_configuration" >}}[Rule Configuration] array_ (mandatory)
+
List of the actual rules.

.Rule set with two rules
====
[source, yaml]
----
version: "1"
name: my-rule-set
rules:
- id: rule:1
match:
url: https://my-service1.local/<**>
Expand Down
12 changes: 4 additions & 8 deletions docs/content/docs/configuration/rules/providers.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ menu:
parent: "Rules"
---

Providers define the sources to load the link:{{< relref "configuration.adoc#_rule_set" >}}[Rule Sets] from. These make Heimdall's behavior dynamic. All providers, you want to enable for a Heimdall instance must be configured within the `providers` section of heimdall's `rules` configuration.
Providers define the sources to load the link:{{< relref "configuration.adoc#_rule_set" >}}[Rule Sets] from. These make heimdall's behavior dynamic. All providers, you want to enable for a heimdall instance must be configured within the `providers` section of heimdall's `rules` configuration.

Supported providers, including the corresponding configuration options are described below

== Filesystem

The filesystem provider allows loading of rule sets from a file system. The configuration of this provider goes into the `file_system` property. This provider is handy for e.g. starting playing around with heimdall, e.g. locally, or using Docker, as well as if your deployment strategy considers deploying a heimdall instance as a Side-Car for each of your services.
The filesystem provider allows loading of rule sets in a format defined in link:{{< relref "configuration.adoc#_rule_set" >}}[Rule Sets] from a file system. The configuration of this provider goes into the `file_system` property. This provider is handy for e.g. starting playing around with heimdall, e.g. locally, or using Docker, as well as if your deployment strategy considers deploying a heimdall instance as a Side-Car for each of your services.

Following configuration options are supported:

Expand All @@ -27,8 +27,6 @@ Can either be a single file, containing a rule set, or a directory with files, e
+
Whether the configured `src` should be watched for updates. Defaults to `false`. If the `src` has been configured to a single file, the provider will watch for changes in that file. Otherwise, if the `src` has been configured to a directory, the provider will watch for files appearing and disappearing in this directory, as well as for changes in each particular file in this directory. Recursive lookup is not supported. That is, if the configured directory contains further directories, these, as well as their contents are ignored.

This provider doesn't need any additional configuration for a rule set. So the contents of files can be just a list of rules as described in link:{{< relref "configuration.adoc#_rule_set" >}}[Rule Sets].

.Load rule sets from the files residing in the `/path/to/rules/dir` directory and watch for changes.
====
[source, yaml]
Expand All @@ -50,7 +48,7 @@ file_system:

== HTTP Endpoint

This provider allows loading of link:{{< relref "configuration.adoc#_rule_set" >}}[Rule Sets] from any remote endpoint accessible via HTTP(s) and supports rule sets in YAML, as well as in JSON format. The differentiation happens based on the `Content-Type` set in the response from the endpoint, which must be either `application/yaml` or `application/json`, otherwise an error is logged and the response from the endpoint is ignored.
This provider allows loading of rule sets in a format defined in link:{{< relref "configuration.adoc#_rule_set" >}}[Rule Sets] from any remote endpoint accessible via HTTP(s) and supports rule sets in YAML, as well as in JSON format. The differentiation happens based on the `Content-Type` set in the response from the endpoint, which must be either `application/yaml` or `application/json`, otherwise an error is logged and the response from the endpoint is ignored.

The loading and removal of rules happens as follows:

Expand All @@ -76,8 +74,6 @@ This property can be used to create kind of a namespace for the rule sets retrie

NOTE: HTTP caching according to https://www.rfc-editor.org/rfc/rfc7234[RFC 7234] is enabled by default. It can be disabled by setting `enable_http_cache` to `false`.

This provider doesn't need any additional configuration for a rule set. So the contents of files can be just a list of rules as described in link:{{< relref "configuration.adoc#_rule_set" >}}[Rule Sets].

.Minimal possible configuration
====
Here the provider is configured to load a rule set from one endpoint without polling it for changes.
Expand Down Expand Up @@ -122,7 +118,7 @@ http_endpoint:

== Cloud Blob

This provider allows loading of link:{{< relref "configuration.adoc#_rule_set" >}}[Rule Sets] from cloud blobs, like AWS S3 buckets, Google Cloud Storage, Azure Blobs, or other API compatible implementations and supports rule sets in YAML, as well as in JSON format. The differentiation happens based on the `Content-Type` set in the metadata of the loaded blob, which must be either `application/yaml` or `application/json`, otherwise an error is logged and the blob is ignored.
This provider allows loading of rule sets in a format defined in link:{{< relref "configuration.adoc#_rule_set" >}}[Rule Sets] from cloud blobs, like AWS S3 buckets, Google Cloud Storage, Azure Blobs, or other API compatible implementations and supports rule sets in YAML, as well as in JSON format. The differentiation happens based on the `Content-Type` set in the metadata of the loaded blob, which must be either `application/yaml` or `application/json`, otherwise an error is logged and the blob is ignored.

The loading and removal of rules happens as follows:

Expand Down
3 changes: 3 additions & 0 deletions example_rules.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
version: v1alpha1
name: test-rule-set
rules:
- id: rule:foo
match:
url: http://foo.bar/<**>
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ require (
github.com/stretchr/testify v1.8.2
github.com/tidwall/gjson v1.14.4
github.com/tonglil/opentelemetry-go-datadog-propagator v0.1.0
github.com/undefinedlabs/go-mpatch v1.0.6
github.com/valyala/fasthttp v1.45.0
github.com/ybbus/httpretry v1.0.2
github.com/yl2chen/cidranger v1.0.2
Expand Down Expand Up @@ -85,7 +86,6 @@ require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect
github.com/aws/aws-sdk-go v1.44.200 // indirect
github.com/aws/aws-sdk-go-v2 v1.17.4 // indirect
Expand Down
Loading

0 comments on commit dba0a87

Please sign in to comment.