rulesengine

package
v0.0.0-...-14ef7bc Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 21, 2024 License: Apache-2.0 Imports: 6 Imported by: 0

README

Mage Engine

Mage Engine is a rules engine framework with the intent to take the existing business logic that dictates configuring the environment and test execution we have accumulated over time in our magefile and organize them better.

The core of what drives the rules will still be the functions we write that check for specific conditions to be met and actions to take on said conditions. The framework just allows us to take those higher order functions and compose them together into more descriptive rules.

There is NO INTENT to be a full fledged rules engine to be able to evaluate very complex expressions or assign priority to rules if multiple rules evaluate to true. As of right now it wasn't obvious if we had such complex requirements based on the data we use.

Architecture

The following Architecture of MageEngine.

Rule

The core of the engine is a Rule. A Rule describes and executes the business logic. It is composed of a Conditional which implements the checking of data that should be evaluated to true. Once a conditional of a Rule evaluates true, the Rule can take an Action. The action implements the execution of the task the rule should take.

A Rule implements the IRule interface:

  • To evaluate the registered condition the engine calls Eval() on the rule.
  • To take action when evaluation is true the engine calls Apply() on the rule.
  • To simulate an action on a rule the engine can call DryRun() (DryRun needs to be set on the RuleCtx for the framework to make this call.)
Rule Context

a RuleCtx is the context object to insert data into and gets passed around so that rules can evaluate and take action. In our use case we have very specific key pieces of data that triggers our business logic so this isn't meant for generalized use cases.

That said, the RuleCtx implements a AddRuleData(key string, obj any) and GetRuleData(key string) functions that would allow a developer to store other types of data. It is important to note that the writer of a conditional or action that depends on data stored in this manner will have to make sure they type cast the returned object appropriately in their implementation.

Conditionals

Conditional is an interface that is meant to perform a check of the passed in data. A conditional can be:

  • a full fledged object/struct that implements the Check() function
  • any anonymous or higher order function as long as it is registered in the Rule as a ConditionalFunc
Filters

To make constructing Rules with some complex conditionals easier the framework implements a couple of filters which implement the Conditional interface:

  • All: Conditional|ConditionalFunc registered will evaluate that all evaluated to true
  • Any: Conditional|ConditionalFunc registered will evaluate that one evaluated to true
  • None: Conditional|ConditionalFunc registered will evaluate that all have NOT evaluated to true. The absence of true
Actions

Action is an interface that is meant to execute on the passed in data. Like a conditional, an action can be:

  • a full fledged object/struct that implements the Execute() function
  • any anonymous or higher order function as long as it is registered in the Rule as a ActionFunc
RuleCatalog

This is really a collection/slice of Rules. You create a catalog and register the catalog with the engine. As of now most of the Business Logic really is tied by repo. So catalogs are created by repo/domain

Engine

It is a map of a map. The idea was that each repo would create a catalog of Rules. Those catalogs could be registered as map to a category. Using the example of test execution, a category could be tests. Then under tests we regsiter a map, where a key is the repo/domain, i.e. e2e-repo and its test catalog is assigned to it.

RuleChain

A RuleChain is more of a concept than it is an actual type. A rulechain is a Rule type but it also implements the Conditional interface. So what does that mean? A rule can now be composed of other rules by registering them as conditionals.

Since Rules are conditionals as well. The evaluation process is a little different. When a RuleChain evaluates a rule it will run the Eval() and Apply()|DryRun(). Both those calls have to execute without fail for the Rule to evaluate true.

This will help reuse existing rules to create broader flows.

Example of constructing Rules/RulesCatalogs/RuleChains

Creating a basic rule using anonymous functions and its rule catalog

In this example, we create a rule for infra-deployments test execution using anonymous condition and action functions. Some conditions and actions may be so simple that it is easier to implement as anonymous functions. I then define this Rule within a Rule Catalog.

The logic in this rule is straightforward: WHEN the RepoName is infra-deployments, THEN execute ginkgo with the specific label filter.

// Example rule for infra-deployments using anonymous functions embedded in the rule
var InfraDeploymentsTestRulesCatalog = rulesengine.RuleCatalog{
   rulesengine.Rule{Name: "Infra Deployments Default Test Execution",
   	Description: "Run the default test suites which include the demo and components suites.",
   	Condition:    rulesengine.ConditionFunc(func(rctx *rulesengine.RuleCtx) (bool, error) {
   		if rctx.RepoName == "infra-deployments" {
   			return true
   		}
   		return false
   	}),
   	Actions: []rulesengine.Action{rulesengine.ActionFunc(func(rctx *rulesengine.RuleCtx) error {
   		rctx.LabelFilter = "e2e-demo,konflux-demo,spi-suite,remote-secret,integration-service,ec,build-templates,multi-platform"
   		return ExecuteTestAction(rctx)
   	})}},

} 

Registering a rule catalog to the engine

Here I've created a category called tests within the MageEngine and I've assigned infra-deployments catalog to the infra-deployments key under the tests category

var MageEngine = rulesengine.RuleEngine{
   "tests": {
   	"infra-deployments": testselection.InfraDeploymentsTestRulesCatalog,
   },
}
Executing the MageEngine within a mage file

This will cause the engine to load and iterate over the rules of the category test and apply any that had their conditions match.


   rctx := rulesengine.NewRuleCtx()
   rctx.DryRun = true

   err = engine.MageEngine.RunRulesOfCategory("tests", rctx)
Creating a complex rule using a mixture of conditional filters

In this example, the release-catalog-repo has a slightly more complex set of rules where depending on certain conditions met by the CI job the test should execute with a different set of ginkgo label filters. So you will see a mixture of the Any/All/None conditional filters

The first rule's logic is: WHEN RepoName equals release-service-catalog AND EITHER NOT pr paired OR is a rehearse job THEN run ginkgo with release-pipelines label filter

The second rule's logic is: WHEN RepoName equals release-service-catalog AND pr is paired AND NOT a rehearse job THEN run ginkgo with release-pipelines && !fbc-tests label filters


rulesengine.Rule{Name: "Release Catalog Test Execution",
   	Description: "Runs all release catalog tests on release-service-catalog repo on PR/rehearsal jobs.",
   	Condition:    rulesengine.All{rulesengine.ConditionFunc(releaseCatalogRepoCondition), rulesengine.Any{rulesengine.None{rulesengine.ConditionFunc(isPaired)},rulesengine.ConditionFunc(isRehearse)},},
   	Actions: []rulesengine.Action{rulesengine.ActionFunc(func(rctx *rulesengine.RuleCtx) error {
   		rctx.LabelFilter = "release-pipelines"
   		return ExecuteTestAction(rctx)
   	})}},
rulesengine.Rule{Name: "Release Catalog PR paired Test Execution",
   	Description: "Runs release catalog tests except for the fbc tests on release-service-catalog repo when PR paired and not a rehearsal job.",
   	Condition:    rulesengine.All{rulesengine.ConditionFunc(releaseCatalogRepoCondition), rulesengine.ConditionFunc(isPaired), rulesengine.None{rulesengine.ConditionFunc(isRehearse)}},
   	Actions: []rulesengine.Action{rulesengine.ActionFunc(func(rctx *rulesengine.RuleCtx) error {
   		rctx.LabelFilter = "release-pipelines && !fbc-tests"
   		return ExecuteTestAction(rctx)
   	})}},

...

var isRehearse = func(rctx *rulesengine.RuleCtx) (bool, error) {
   if strings.Contains(rctx.JobName, "rehearse") {
   	return true
   }

   return false
}

// Demo of func isPRPairingRequired() for testing purposes
var isPaired = func(rctx *rulesengine.RuleCtx) (bool, error) {
   if true {
   	return true
   }

   return false
}


func releaseCatalogRepoCondition(rctx *rulesengine.RuleCtx) (bool, error) {

   if rctx.RepoName == "release-service-catalog" {
   	return true
   }

   return false

}
Creating a Rule Chain

You can refer to testselection/rule_demo.go to see an example. There we reorganize the LocalE2E mage function which calls PreflightCheck, BootstrapCluster, RunE2ETest, into a rule chain composed of a set of 3 rules.

You can run this demo through mage by running ./mage -v local:runRuleDemo

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Action

type Action interface {
	Execute(rctx *RuleCtx) error
}

type ActionFunc

type ActionFunc func(rctx *RuleCtx) error

func (ActionFunc) Execute

func (af ActionFunc) Execute(rctx *RuleCtx) error

type All

type All []Conditional

func (All) Check

func (a All) Check(rctx *RuleCtx) (bool, error)

type Any

type Any []Conditional

func (Any) Check

func (a Any) Check(rctx *RuleCtx) (bool, error)

type ConditionFunc

type ConditionFunc func(rctx *RuleCtx) (bool, error)

func (ConditionFunc) Check

func (cf ConditionFunc) Check(rctx *RuleCtx) (bool, error)

type Conditional

type Conditional interface {
	Check(rctx *RuleCtx) (bool, error)
}

type File

type File struct {
	Status string
	Name   string
}

type Files

type Files []File

func (*Files) FilterByDirGlob

func (cfs *Files) FilterByDirGlob(filter string) Files

func (*Files) FilterByDirString

func (cfs *Files) FilterByDirString(filter string) Files

func (*Files) FilterByStatus

func (cfs *Files) FilterByStatus(filter string) Files

func (*Files) String

func (cfs *Files) String() string

type IRule

type IRule interface {
	Eval(rctx *RuleCtx) (bool, error)
	Apply(rctx *RuleCtx) error
	DryRun(rctx *RuleCtx) error
}

type IRuleCtx

type IRuleCtx interface {
	AddRuleData(key string, obj any) error
	GetRuleData(key string) any
}

type None

type None []Conditional

func (None) Check

func (a None) Check(rctx *RuleCtx) (bool, error)

type Rule

type Rule struct {
	Name        string
	Description string
	Condition   Conditional
	Actions     []Action
}

func (*Rule) Apply

func (r *Rule) Apply(rctx *RuleCtx) error

func (*Rule) Check

func (r *Rule) Check(rctx *RuleCtx) (bool, error)

func (*Rule) DryRun

func (r *Rule) DryRun(rctx *RuleCtx) error

func (*Rule) Eval

func (r *Rule) Eval(rctx *RuleCtx) (bool, error)

func (*Rule) String

func (r *Rule) String() string

type RuleCatalog

type RuleCatalog []Rule

func (*RuleCatalog) String

func (rc *RuleCatalog) String() string

type RuleCtx

type RuleCtx struct {
	types.CLIConfig
	types.SuiteConfig
	types.ReporterConfig
	types.GoFlagsConfig
	RuleData                      map[string]any
	RepoName                      string
	ComponentImageTag             string
	ComponentEnvVarPrefix         string
	JobName                       string
	JobType                       string
	DiffFiles                     Files
	IsPaired                      bool
	RequiredBinaries              []string
	PrRemoteName                  string
	PrCommitSha                   string
	PrBranchName                  string
	RequiresMultiPlatformTests    bool
	RequiresSprayProxyRegistering bool
}

func NewRuleCtx

func NewRuleCtx() *RuleCtx

func (*RuleCtx) AddRuleData

func (gca *RuleCtx) AddRuleData(key string, obj any) error

func (*RuleCtx) GetRuleData

func (gca *RuleCtx) GetRuleData(key string) any

type RuleEngine

type RuleEngine map[string]map[string]RuleCatalog

func (*RuleEngine) ListCatagoriesOfCatalogs

func (e *RuleEngine) ListCatagoriesOfCatalogs() string

func (*RuleEngine) ListCatalogsByCategory

func (e *RuleEngine) ListCatalogsByCategory(cat string) (string, error)

func (*RuleEngine) RunRules

func (e *RuleEngine) RunRules(rctx *RuleCtx, args ...string) error

func (*RuleEngine) RunRulesOfCategory

func (e *RuleEngine) RunRulesOfCategory(cat string, rctx *RuleCtx) error

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL