dsl

package
v0.12.1 Latest Latest
Warning

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

Go to latest
Published: Dec 22, 2023 License: Apache-2.0 Imports: 34 Imported by: 0

README

Hops DSL

The hops dsl package provides definitions of the hops syntax and parsing logic.

Core functions and expressions within the syntax are handled here, though dispatch and orchestration logic are not.

Documentation

Overview

c matches character c (c != '\\', '-', ']') '\\' c matches character c lo '-' hi matches character c for lo <= c <= hi

Index

Constants

View Source
const (
	HopsExt   = ".hops"
	HopsFile  = "hops"
	OtherFile = "other"
)
View Source
const (
	InvalidRequired  string = "Required"
	InvalidNotString string = "Should be a string"
	InvalidNotText   string = "Should be text"
	InvalidNotNumber string = "Should be a number"
	InvalidNotBool   string = "Should be a boolean"
)
View Source
const MaxLabelLength = 50

Variables

View Source
var (
	ErrorAttr  = "error"
	ResultAttr = "result"
	IfAttr     = "if"
	NameAttr   = "name"

	HopSchema = &hcl.BodySchema{
		Attributes: []hcl.AttributeSchema{},
		Blocks: []hcl.BlockHeaderSchema{
			{
				Type:       OnID,
				LabelNames: []string{"eventType"},
			},
			{
				Type:       TaskID,
				LabelNames: []string{"Name"},
			},
			{
				Type:       ScheduleID,
				LabelNames: []string{"Name"},
			},
		},
	}

	OnID     = "on"
	OnSchema = &hcl.BodySchema{
		Blocks: []hcl.BlockHeaderSchema{
			{
				Type:       CallID,
				LabelNames: []string{"taskType"},
			},
			{
				Type: DoneID,
			},
		},
		Attributes: []hcl.AttributeSchema{
			{Name: "name", Required: false},
			{Name: IfAttr, Required: false},
		},
	}

	CallID = "call"

	DoneID = "done"

	TaskID = "task"

	ParamID    = "param"    // Schema defined via tags on the struct
	ScheduleID = "schedule" // Schema defined via tags on the struct
)
View Source
var AllTrueFunc = function.New(&function.Spec{
	VarParam: &function.Parameter{
		Name: "clauses",
		Type: cty.Bool,
	},
	Type: function.StaticReturnType(cty.Bool),
	Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
		return AllTrue(args)
	},
})

alltrue() takes a variable number of bool arguments and returns true if all of them are true.

View Source
var AnyTrueFunc = function.New(&function.Spec{
	VarParam: &function.Parameter{
		Name: "clauses",
		Type: cty.Bool,
	},
	Type: function.StaticReturnType(cty.Bool),
	Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
		if len(args) == 0 {
			return cty.False, nil
		}

		for _, clause := range args {
			if clause.True() {
				return cty.True, nil
			}
		}

		return cty.False, nil
	},
})

anytrue() takes a variable number of bool arguments and returns true if any of them are true.

View Source
var EnvFunc = function.New(&function.Spec{
	Params: []function.Parameter{
		{
			Name: "envVarName",
			Type: cty.String,
		},
		{
			Name: "defaultValue",
			Type: cty.String,
		},
	},
	Type: function.StaticReturnType(cty.String),
	Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
		envVarName := args[0]
		defaultValue := args[1]
		return Env(envVarName, defaultValue)
	},
})

EnvFunc is a cty.Function that returns an env var or the default value if it doesn't exist

View Source
var ExclusiveGlobFunc = function.New(&function.Spec{
	Params: []function.Parameter{
		{
			Name: "patterns",
			Type: cty.DynamicPseudoType,
		},
		{
			Name: "values",
			Type: cty.DynamicPseudoType,
		},
	},
	Type: function.StaticReturnType(cty.Bool),
	Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
		values := args[0]
		patterns := args[1]
		return XGlob(values, patterns)
	},
})

A cty.Function that matches a string or list of strings against a glob pattern or list of glob patterns. Returns true if _all_ values match at least one pattern.

View Source
var GlobFunc = function.New(&function.Spec{
	Params: []function.Parameter{
		{
			Name: "patterns",
			Type: cty.DynamicPseudoType,
		},
		{
			Name: "values",
			Type: cty.DynamicPseudoType,
		},
	},
	Type: function.StaticReturnType(cty.Bool),
	Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
		values := args[0]
		patterns := args[1]
		return Glob(values, patterns)
	},
})

GlobFunc is a cty.Function that matches a string or list of strings against a glob pattern or list of glob patterns. Returns true if _any_ values match at least one pattern.

View Source
var StatelessFunctions = map[string]function.Function{
	"abs":             stdlib.AbsoluteFunc,
	"alltrue":         AllTrueFunc,
	"anytrue":         AnyTrueFunc,
	"versiontmpl":     VersionTemplateFunc,
	"can":             tryfunc.CanFunc,
	"ceil":            stdlib.CeilFunc,
	"chomp":           stdlib.ChompFunc,
	"coalesce":        stdlib.CoalesceFunc,
	"compact":         stdlib.CompactFunc,
	"concat":          stdlib.ConcatFunc,
	"csv":             stdlib.CSVDecodeFunc,
	"env":             EnvFunc,
	"flatten":         stdlib.FlattenFunc,
	"floor":           stdlib.FloorFunc,
	"format":          stdlib.FormatFunc,
	"formatdate":      stdlib.FormatDateFunc,
	"glob":            GlobFunc,
	"indent":          stdlib.IndentFunc,
	"index":           stdlib.IndexFunc,
	"int":             stdlib.IntFunc,
	"join":            stdlib.JoinFunc,
	"jsondecode":      stdlib.JSONDecodeFunc,
	"jsonencode":      stdlib.JSONEncodeFunc,
	"keys":            stdlib.KeysFunc,
	"length":          stdlib.LengthFunc,
	"lookup":          stdlib.LookupFunc,
	"lower":           stdlib.LowerFunc,
	"max":             stdlib.MaxFunc,
	"merge":           stdlib.MergeFunc,
	"min":             stdlib.MinFunc,
	"range":           stdlib.RangeFunc,
	"regex":           stdlib.RegexAllFunc,
	"regexreplace":    stdlib.RegexReplaceFunc,
	"replace":         stdlib.ReplaceFunc,
	"reverse":         stdlib.ReverseFunc,
	"setintersection": stdlib.SetIntersectionFunc,
	"setproduct":      stdlib.SetProductFunc,
	"setunion":        stdlib.SetUnionFunc,
	"slice":           stdlib.SliceFunc,
	"sort":            stdlib.SortFunc,
	"split":           stdlib.SplitFunc,
	"strlen":          stdlib.StrlenFunc,
	"substr":          stdlib.SubstrFunc,
	"timeadd":         stdlib.TimeAddFunc,
	"title":           stdlib.TitleFunc,
	"tobool":          stdlib.MakeToFunc(cty.Bool),
	"tolist":          stdlib.MakeToFunc(cty.List(cty.DynamicPseudoType)),
	"tomap":           stdlib.MakeToFunc(cty.Map(cty.DynamicPseudoType)),
	"tonumber":        stdlib.MakeToFunc(cty.Number),
	"toset":           stdlib.MakeToFunc(cty.Set(cty.DynamicPseudoType)),
	"tostring":        stdlib.MakeToFunc(cty.String),
	"trim":            stdlib.TrimFunc,
	"trimprefix":      stdlib.TrimPrefixFunc,
	"trimspace":       stdlib.TrimSpaceFunc,
	"trimsuffix":      stdlib.TrimSuffixFunc,
	"try":             tryfunc.TryFunc,
	"upper":           stdlib.UpperFunc,
	"values":          stdlib.ValuesFunc,
	"xglob":           ExclusiveGlobFunc,
	"zipmap":          stdlib.ZipmapFunc,
}

StatelessFunctions can be instantiated once TODO: Add encode/decode b64

View Source
var VersionTemplateFunc = function.New(&function.Spec{
	Params: []function.Parameter{
		{
			Name: "template",
			Type: cty.String,
		},
	},
	Type: function.StaticReturnType(cty.String),
	Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
		templateVal := args[0]
		template := templateVal.AsString()

		version, err := TemplateVersion(template)

		return cty.StringVal(version), err
	},
})

VersionTemplateFunc generates a calver or pet version according to a template

Functions

func AllTrue added in v0.2.0

func AllTrue(values []cty.Value) (cty.Value, error)

func AnyJSONToCtyValue

func AnyJSONToCtyValue(jsonStr []byte) (cty.Value, error)

TODO: This method effectively parses the JSON string twice. Once via unmarshal called directly, then again via ctyjson.ImpliedType which runs a decoder. It is likely worth the time to write a decoder that directly takes the unmarshalled json and manually maps to cty values. This function is quite expensive as an overall portion of the runtime. Taking around 200-250µs for a single change event (around 20-25% of hops parsing time).

func AnyTrue added in v0.2.0

func AnyTrue(values []cty.Value) (cty.Value, error)

func CreateSourceEvent

func CreateSourceEvent(rawEvent map[string]any, source string, event string, action string) ([]byte, string, error)

func DecodeCallBlock

func DecodeCallBlock(ctx context.Context, hop *HopAST, on *OnAST, block *hcl.Block, idx int, evalctx *hcl.EvalContext, logger zerolog.Logger) error

func DecodeConditionalAttr

func DecodeConditionalAttr(attr *hcl.Attribute, defaultValue bool, ctx *hcl.EvalContext) (bool, error)

func DecodeHopsBody

func DecodeHopsBody(ctx context.Context, hop *HopAST, hops *HopsFiles, evalctx *hcl.EvalContext, logger zerolog.Logger) error

func DecodeNameAttr

func DecodeNameAttr(attr *hcl.Attribute) (string, error)

func DecodeOnBlock

func DecodeOnBlock(ctx context.Context, hop *HopAST, hops *HopsFiles, block *hcl.Block, idx int, evalctx *hcl.EvalContext, logger zerolog.Logger) error

func DecodeParamBlock

func DecodeParamBlock(block *hcl.Block, task *TaskAST, hop *HopAST, evalctx *hcl.EvalContext) error

func DecodeScheduleBlock added in v0.7.0

func DecodeScheduleBlock(block *hcl.Block, hop *HopAST, evalctx *hcl.EvalContext) error

func DecodeSchedules added in v0.7.0

func DecodeSchedules(hop *HopAST, hops *HopsFiles, evalctx *hcl.EvalContext) error

func DecodeTaskBlock

func DecodeTaskBlock(ctx context.Context, hop *HopAST, block *hcl.Block, evalctx *hcl.EvalContext) error

func DecodeTasks

func DecodeTasks(ctx context.Context, hop *HopAST, hops *HopsFiles, evalctx *hcl.EvalContext) error

func Env

func Env(envVarName, defaultValue cty.Value) (cty.Value, error)

func File added in v0.12.0

func File(directory, filename string, hops *HopsFiles) (string, error)

File returns the content of a file from the HopsFiles struct.

Default file path is the directory that is passed in.

func FileFunc added in v0.12.0

func FileFunc(hops *HopsFiles, hopsDirectory string) function.Function

FileFunc is a stateful cty function that returns the contents of a file relative to the current .hops file. The data of the file is determined at startup time.

It is stateful because it requires the HopsFiles struct and the hopsDirectory to be passed in.

func Glob

func Glob(values, patterns cty.Value) (cty.Value, error)

func ReadHopsFileContents added in v0.3.0

func ReadHopsFileContents(hopsFileContent []FileContent) (*hcl.BodyContent, string, error)

func StatefulFunctions added in v0.12.0

func StatefulFunctions(hops *HopsFiles, hopsDirectory string) map[string]function.Function

StatefulFunctions returns a map of all stateful functions that are scoped to a block (and therefore a single .hops file).

These must be added to the eval context for each block as the contents change. This is a workaround to the fact that cty does not allow passing additional context information beyond the parameters of the function. And specifically does not allow passing the eval context.

func Template added in v0.12.0

func Template(directory, filename string, hops *HopsFiles, variables map[string]any) (string, error)

Template returns the evaluated template content of a file from the HopsFiles struct and the passed in variables. Handles special case where "autoescape" is desired.

Default file path is the directory that is passed in. If "autoescape" is true, then the template is wrapped in autoescape tags which protects against dangerous HTML inputs in variables.

func TemplateFunc added in v0.12.0

func TemplateFunc(hops *HopsFiles, hopsDirectory string) function.Function

TemplateFunc is a stateful cty function that evaluates a file and variables using the pongo2 library, which matches the Django template language. It returns the results as a string. It finds the file relative to the current .hops file. The data of the file is determined at startup time.

It is stateful because it requires the HopsFiles struct and the hopsDirectory to be passed in.

func TemplateVersion added in v0.3.0

func TemplateVersion(template string) (string, error)

func ValidateLabels

func ValidateLabels(labels ...string) error

func XGlob

func XGlob(values, patterns cty.Value) (cty.Value, error)

Types

type CallAST

type CallAST struct {
	Slug     string
	TaskType string
	Name     string
	Inputs   []byte
	ConditionalAST
}

type ConditionalAST

type ConditionalAST struct {
	IfClause bool
}

type DoneAST added in v0.6.0

type DoneAST struct {
	Error  error
	Result []byte
}

func DecodeDoneBlock added in v0.6.0

func DecodeDoneBlock(ctx context.Context, hop *HopAST, on *OnAST, block *hcl.Block, evalctx *hcl.EvalContext, logger zerolog.Logger) (*DoneAST, error)

type FileContent added in v0.3.0

type FileContent struct {
	File    string `json:"file"`
	Content []byte `json:"content"`
	Type    string `json:"type"`
}

type HopAST

type HopAST struct {
	Ons          []OnAST
	Schedules    []ScheduleAST
	SlugRegister map[string]bool
	StartedAt    time.Time
	Tasks        []TaskAST
}

func ParseHops

func ParseHops(ctx context.Context, hops *HopsFiles, eventBundle map[string][]byte, logger zerolog.Logger) (*HopAST, error)

func ParseHopsSchedules added in v0.7.0

func ParseHopsSchedules(hops *HopsFiles, logger zerolog.Logger) (*HopAST, error)

func ParseHopsTasks

func ParseHopsTasks(ctx context.Context, hops *HopsFiles) (*HopAST, error)

func (*HopAST) GetTask

func (h *HopAST) GetTask(taskName string) (TaskAST, error)

func (*HopAST) ListSchedules added in v0.7.0

func (h *HopAST) ListSchedules() []ScheduleAST

func (*HopAST) ListTasks

func (h *HopAST) ListTasks() []TaskAST

type HopsFiles added in v0.3.0

type HopsFiles struct {
	Hash        string
	BodyContent *hcl.BodyContent
	Files       []FileContent // Sorted by file name `File`
}

func ReadHopsFilePath added in v0.3.0

func ReadHopsFilePath(filePath string) (*HopsFiles, error)

ReadHopsFilePath loads and pre-parses the content of .hops files from all .hops files in the first child sub directories.

It returns a merged hcl.Body and a sha hash of the contents as well as a slice of FileContent structs containing the file name, content and type.

func (*HopsFiles) LookupFile added in v0.12.0

func (h *HopsFiles) LookupFile(filePath string) (*FileContent, bool)

LookupFile searches for a file in the HopsFiles struct and returns a reference to the file and true if found, or nil and false if not found.

type OnAST

type OnAST struct {
	Slug      string
	EventType string
	Name      string
	Calls     []CallAST
	Done      *DoneAST
	ConditionalAST
}

type ParamAST

type ParamAST struct {
	// We use HCL tags to auto-decode as params need very little custom decoding logic
	Name        string `hcl:"name,label" json:"name"`
	DisplayName string `hcl:"display_name,optional" json:"display_name"`
	Type        string `hcl:"type,optional" json:"type"`
	Default     any    `hcl:"default,optional" json:"default"`
	Help        string `hcl:"help,optional" json:"help"`
	Flag        string `hcl:"flag,optional" json:"flag"`
	ShortFlag   string `hcl:"shortflag,optional" json:"shortflag"`
	Required    bool   `hcl:"required,optional" json:"required"`
}

type ScheduleAST added in v0.7.0

type ScheduleAST struct {
	Name   string         `hcl:"name,label" json:"name"`
	Cron   string         `hcl:"cron,attr" json:"cron"`
	Inputs []byte         `json:"inputs"` // Inputs is decoded explicitly from remain
	Remain hcl.Attributes `hcl:",remain"`
}

type SourceMeta

type SourceMeta struct {
	Source string `json:"source"`
	Event  string `json:"event"`
	Action string `json:"action"`
}

type TaskAST

type TaskAST struct {
	Name        string     `json:"name"`
	DisplayName string     `json:"display_name"`
	Summary     string     `json:"summary"`
	Description string     `json:"description"`
	Emoji       string     `json:"emoji"`
	Params      []ParamAST `json:"params"`
}

func (*TaskAST) ValidateInput

func (c *TaskAST) ValidateInput(input map[string]any) map[string][]string

ValidateInput validates a struct of param inputs against a task

Returns a map of parameter names with an array of validation error messages if any. Map will be empty (but not nil) if all input is valid.

Jump to

Keyboard shortcuts

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