Skip to content
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
30 changes: 16 additions & 14 deletions pkg/execs/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package execs
import (
"errors"
"fmt"
"regexp"
"slices"
"strings"
)
Expand All @@ -30,7 +29,7 @@ type EnvFromSource struct {

// CallerRef represents a reference to environment variables from the caller process.
type CallerRef struct {
compiledPattern *regexp.Regexp // Compiled regex pattern for matching environment variables.
compiledPattern *LazyRegexp // Compiled regex pattern for matching environment variables.

// Pattern is a regex pattern for matching environment variable names.
Pattern string `json:"pattern,omitempty" jsonschema:"title=Pattern,format=regex"`
Expand All @@ -56,16 +55,17 @@ type EnvVarSource struct {

// Compile compiles the caller reference pattern into a regex if a pattern is provided.
func (c *CallerRef) Compile() error {
if c.compiledPattern == nil && c.Pattern != "" {
pattern, err := regexp.Compile(c.Pattern)
if err != nil {
return fmt.Errorf("compile pattern %q: %w", c.Pattern, err)
}
if c.Pattern == "" {
return nil
}

c.compiledPattern = pattern
if c.compiledPattern == nil {
c.compiledPattern = NewLazyRegexp(c.Pattern)
}

return nil
_, err := c.compiledPattern.Get()

return err
}

// Command manages common command execution properties.
Expand Down Expand Up @@ -176,11 +176,13 @@ func (e *Command) applyEnvFrom(envMap map[string]string) {
}

// Handle pattern-based inheritance.
pattern := envFromSource.CallerRef.compiledPattern
if pattern != nil {
for key, value := range e.baseEnv {
if pattern.MatchString(key) {
envMap[key] = value
if envFromSource.CallerRef.compiledPattern != nil {
pattern, err := envFromSource.CallerRef.compiledPattern.Get()
if err == nil && pattern != nil {
for key, value := range e.baseEnv {
if pattern.MatchString(key) {
envMap[key] = value
}
}
}
}
Expand Down
46 changes: 46 additions & 0 deletions pkg/execs/lazy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package execs

import (
"fmt"
"regexp"
"sync"
)

// LazyRegexp provides thread-safe lazy compilation of a regular expression.
// The pattern is compiled at most once, even when accessed concurrently.
type LazyRegexp struct {
err error
regex *regexp.Regexp
pattern string
once sync.Once
}

// NewLazyRegexp creates a new LazyRegexp that will compile the given pattern
// when Get() is first called.
func NewLazyRegexp(pattern string) *LazyRegexp {
return &LazyRegexp{
pattern: pattern,
}
}

// Get returns the compiled regular expression, compiling it on the first call.
// Subsequent calls return the cached result.
func (lr *LazyRegexp) Get() (*regexp.Regexp, error) {
lr.once.Do(func() {
if lr.pattern == "" {
return
}

lr.regex, lr.err = regexp.Compile(lr.pattern)
if lr.err != nil {
lr.err = fmt.Errorf("compile pattern %q: %w", lr.pattern, lr.err)
}
})

return lr.regex, lr.err
}

// IsCompiled returns true if the pattern has been compiled.
func (lr *LazyRegexp) IsCompiled() bool {
return lr.regex != nil || lr.err != nil
}
40 changes: 40 additions & 0 deletions pkg/expr/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,43 @@ func (e *Environment) Compile(expression string) (cel.Program, error) {

return program, nil
}

// LazyProgram provides thread-safe lazy compilation of a CEL expression.
// The expression is compiled at most once, even when accessed concurrently.
type LazyProgram struct {
program cel.Program
err error
env *Environment
expression string
once sync.Once
}

// NewLazyProgram creates a new LazyProgram that will compile the given expression
// using the provided environment when Get() is first called.
func NewLazyProgram(expression string, env *Environment) *LazyProgram {
return &LazyProgram{
expression: expression,
env: env,
}
}

// Get returns the compiled program, compiling it on the first call.
// Subsequent calls return the cached result.
//
//nolint:ireturn // Following CEL's function signature.
func (lp *LazyProgram) Get() (cel.Program, error) {
lp.once.Do(func() {
if lp.expression == "" {
return
}

lp.program, lp.err = lp.env.Compile(lp.expression)
})

return lp.program, lp.err
}

// IsCompiled returns true if the program has been compiled.
func (lp *LazyProgram) IsCompiled() bool {
return lp.program != nil || lp.err != nil
}
51 changes: 35 additions & 16 deletions pkg/profile/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ type StatusManager interface {

// Profile represents a command profile.
type Profile struct {
sourceProgram cel.Program
reloadProgram cel.Program
sourceProgram *expr.LazyProgram
reloadProgram *expr.LazyProgram
executor Executor
status StatusManager

Expand Down Expand Up @@ -247,7 +247,11 @@ func (p *Profile) Build() error {

// CompileSource compiles the profile's source expression into a CEL program.
func (p *Profile) CompileSource() error {
if p.sourceProgram == nil && p.Source != "" {
if p.Source == "" {
return nil
}

if p.sourceProgram == nil {
env, err := expr.NewEnvironment(
cel.Variable("files", cel.ListType(cel.StringType)),
cel.Variable("dir", cel.StringType),
Expand All @@ -256,20 +260,24 @@ func (p *Profile) CompileSource() error {
return fmt.Errorf("environment: %w", err)
}

program, err := env.Compile(p.Source)
if err != nil {
return fmt.Errorf("expression: %w", err)
}
p.sourceProgram = expr.NewLazyProgram(p.Source, env)
}

p.sourceProgram = program
_, err := p.sourceProgram.Get()
if err != nil {
return fmt.Errorf("expression: %w", err)
}

return nil
}

// CompileReload compiles the profile's reload expression into a CEL program.
func (p *Profile) CompileReload() error {
if p.reloadProgram == nil && p.Reload != "" {
if p.Reload == "" {
return nil
}

if p.reloadProgram == nil {
env, err := expr.NewEnvironment(
cel.Variable("file", cel.StringType),
cel.Variable("fs.event", cel.IntType),
Expand All @@ -279,12 +287,12 @@ func (p *Profile) CompileReload() error {
return fmt.Errorf("environment: %w", err)
}

program, err := env.Compile(p.Reload)
if err != nil {
return fmt.Errorf("expression: %w", err)
}
p.reloadProgram = expr.NewLazyProgram(p.Reload, env)
}

p.reloadProgram = program
_, err := p.reloadProgram.Get()
if err != nil {
return fmt.Errorf("expression: %w", err)
}

return nil
Expand All @@ -302,7 +310,13 @@ func (p *Profile) MatchFiles(dirPath string, files []string) (bool, []string) {
return true, nil // If no source expression is defined, use default file filtering.
}

result, _, err := p.sourceProgram.Eval(map[string]any{
program, err := p.sourceProgram.Get()
if err != nil {
// If compilation fails, consider it a non-match.
return false, nil
}

result, _, err := program.Eval(map[string]any{
"files": files,
"dir": dirPath,
})
Expand Down Expand Up @@ -339,13 +353,18 @@ func (p *Profile) MatchFileEvent(filePath string, fsOp fsnotify.Op) (bool, error
return true, nil // If no reload expression is defined, always reload.
}

program, err := p.reloadProgram.Get()
if err != nil {
return false, fmt.Errorf("compile reload expression: %w", err)
}

evalVars := map[string]any{
"file": filePath,
"fs.event": int64(fsOp),
"render": p.status.RenderMap(),
}

result, _, err := p.reloadProgram.Eval(evalVars)
result, _, err := program.Eval(evalVars)
if err != nil {
return false, fmt.Errorf("evaluate reload expression: %w", err)
}
Expand Down
24 changes: 17 additions & 7 deletions pkg/rule/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import (
//
// Use the `in` operator to check membership in lists, e.g.: pathBase(f) in ["Chart.yaml"].
type Rule struct {
matchProgram cel.Program // Compiled CEL program for matching file paths.
matchProgram *expr.LazyProgram // Compiled CEL program for matching file paths.

// Match is a CEL expression to match file paths.
Match string `json:"match" jsonschema:"title=Match Expression"`
Expand Down Expand Up @@ -69,6 +69,10 @@ func MustNew(profileName, match string) *Rule {

// CompileMatch compiles the rule's match expression into a CEL program.
func (r *Rule) CompileMatch() error {
if r.Match == "" {
return errors.New("compile match expression: match expression is required")
}

if r.matchProgram == nil {
env, err := expr.NewEnvironment(
cel.Variable("files", cel.ListType(cel.StringType)),
Expand All @@ -78,12 +82,12 @@ func (r *Rule) CompileMatch() error {
return fmt.Errorf("create CEL environment: %w", err)
}

program, err := env.Compile(r.Match)
if err != nil {
return fmt.Errorf("compile match expression: %w", err)
}
r.matchProgram = expr.NewLazyProgram(r.Match, env)
}

r.matchProgram = program
_, err := r.matchProgram.Get()
if err != nil {
return fmt.Errorf("compile match expression: %w", err)
}

return nil
Expand All @@ -99,7 +103,13 @@ func (r *Rule) MatchFiles(dirPath string, files []string) bool {
panic(errors.New("rule missing a match expression"))
}

result, _, err := r.matchProgram.Eval(map[string]any{
program, err := r.matchProgram.Get()
if err != nil {
// If compilation fails, consider it a non-match.
return false
}

result, _, err := program.Eval(map[string]any{
"files": files,
"dir": dirPath,
})
Expand Down