...

Source file src/edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/policy/predicate/file.go

Documentation: edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/policy/predicate

     1  // Copyright 2018 Palantir Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package predicate
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"strconv"
    22  
    23  	"github.com/pkg/errors"
    24  
    25  	"edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/pull"
    26  
    27  	"edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/policy/common"
    28  )
    29  
    30  type ChangedFiles struct {
    31  	Paths       []common.Regexp `yaml:"paths,omitempty"`
    32  	IgnorePaths []common.Regexp `yaml:"ignore,omitempty"`
    33  }
    34  
    35  var _ Predicate = &ChangedFiles{}
    36  
    37  func (pred *ChangedFiles) Evaluate(ctx context.Context, prctx pull.Context) (*common.PredicateResult, error) {
    38  	var paths, ignorePaths []string
    39  
    40  	for _, path := range pred.Paths {
    41  		paths = append(paths, path.String())
    42  	}
    43  
    44  	for _, ignorePath := range pred.IgnorePaths {
    45  		ignorePaths = append(ignorePaths, ignorePath.String())
    46  	}
    47  
    48  	predicateResult := common.PredicateResult{
    49  		ValuePhrase:     "changed files",
    50  		ConditionPhrase: "match",
    51  		ConditionsMap: map[string][]string{
    52  			"path patterns":  paths,
    53  			"while ignoring": ignorePaths,
    54  		},
    55  	}
    56  
    57  	files, err := prctx.ChangedFiles()
    58  	if err != nil {
    59  		return nil, errors.Wrap(err, "failed to list changed files")
    60  	}
    61  
    62  	changedFiles := []string{}
    63  
    64  	for _, f := range files {
    65  
    66  		changedFiles = append(changedFiles, f.Filename)
    67  
    68  		if anyMatches(pred.IgnorePaths, f.Filename) {
    69  			continue
    70  		}
    71  
    72  		if anyMatches(pred.Paths, f.Filename) {
    73  			predicateResult.Values = []string{f.Filename}
    74  			predicateResult.Description = f.Filename + " was changed"
    75  			predicateResult.Satisfied = true
    76  			return &predicateResult, nil
    77  		}
    78  	}
    79  
    80  	predicateResult.Values = changedFiles
    81  	predicateResult.Description = "No changed files match the required patterns"
    82  	predicateResult.Satisfied = false
    83  	return &predicateResult, nil
    84  }
    85  
    86  func (pred *ChangedFiles) Trigger() common.Trigger {
    87  	return common.TriggerCommit
    88  }
    89  
    90  type OnlyChangedFiles struct {
    91  	Paths []common.Regexp `yaml:"paths,omitempty"`
    92  }
    93  
    94  var _ Predicate = &OnlyChangedFiles{}
    95  
    96  func (pred *OnlyChangedFiles) Evaluate(ctx context.Context, prctx pull.Context) (*common.PredicateResult, error) {
    97  	var paths []string
    98  
    99  	for _, path := range pred.Paths {
   100  		paths = append(paths, path.String())
   101  	}
   102  
   103  	predicateResult := common.PredicateResult{
   104  		ValuePhrase:     "changed files",
   105  		ConditionPhrase: "all match patterns",
   106  		ConditionValues: paths,
   107  	}
   108  
   109  	files, err := prctx.ChangedFiles()
   110  	if err != nil {
   111  		return nil, errors.Wrap(err, "failed to list changed files")
   112  	}
   113  
   114  	changedFiles := []string{}
   115  
   116  	for _, f := range files {
   117  
   118  		changedFiles = append(changedFiles, f.Filename)
   119  
   120  		if anyMatches(pred.Paths, f.Filename) {
   121  			continue
   122  		}
   123  		predicateResult.Values = []string{f.Filename}
   124  		predicateResult.Description = "A changed file does not match the required pattern"
   125  		predicateResult.Satisfied = false
   126  		return &predicateResult, nil
   127  	}
   128  
   129  	filesChanged := len(files) > 0
   130  
   131  	desc := ""
   132  	if !filesChanged {
   133  		desc = "No files changed"
   134  	}
   135  
   136  	predicateResult.Values = changedFiles
   137  	predicateResult.Description = desc
   138  	predicateResult.Satisfied = filesChanged
   139  	return &predicateResult, nil
   140  }
   141  
   142  func (pred *OnlyChangedFiles) Trigger() common.Trigger {
   143  	return common.TriggerCommit
   144  }
   145  
   146  type ModifiedLines struct {
   147  	Additions ComparisonExpr `yaml:"additions"`
   148  	Deletions ComparisonExpr `yaml:"deletions"`
   149  	Total     ComparisonExpr `yaml:"total"`
   150  }
   151  
   152  type CompareOp uint8
   153  
   154  const (
   155  	OpNone CompareOp = iota
   156  	OpLessThan
   157  	OpGreaterThan
   158  )
   159  
   160  type ComparisonExpr struct {
   161  	Op    CompareOp
   162  	Value int64
   163  }
   164  
   165  func (exp ComparisonExpr) IsEmpty() bool {
   166  	return exp.Op == OpNone && exp.Value == 0
   167  }
   168  
   169  func (exp ComparisonExpr) Evaluate(n int64) bool {
   170  	switch exp.Op {
   171  	case OpLessThan:
   172  		return n < exp.Value
   173  	case OpGreaterThan:
   174  		return n > exp.Value
   175  	}
   176  	return false
   177  }
   178  
   179  func (exp ComparisonExpr) MarshalText() ([]byte, error) {
   180  	if exp.Op == OpNone {
   181  		return nil, nil
   182  	}
   183  
   184  	var op string
   185  	switch exp.Op {
   186  	case OpLessThan:
   187  		op = "<"
   188  	case OpGreaterThan:
   189  		op = ">"
   190  	default:
   191  		return nil, errors.Errorf("unknown operation: %d", exp.Op)
   192  	}
   193  	return []byte(fmt.Sprintf("%s %d", op, exp.Value)), nil
   194  }
   195  
   196  func (exp ComparisonExpr) String() string {
   197  	res, err := exp.MarshalText()
   198  	if err != nil {
   199  		return fmt.Sprintf("?? (op:%d) %d", exp.Op, exp.Value)
   200  	}
   201  	return string(res[:])
   202  }
   203  
   204  func (exp *ComparisonExpr) UnmarshalText(text []byte) error {
   205  	text = bytes.TrimSpace(text)
   206  	if len(text) == 0 {
   207  		*exp = ComparisonExpr{}
   208  		return nil
   209  	}
   210  
   211  	i := 0
   212  	var op CompareOp
   213  	switch text[i] {
   214  	case '<':
   215  		op = OpLessThan
   216  	case '>':
   217  		op = OpGreaterThan
   218  	default:
   219  		return errors.Errorf("invalid comparison operator: %c", text[i])
   220  	}
   221  
   222  	i++
   223  	for i < len(text) && (text[i] == ' ' || text[i] == '\t') {
   224  		i++
   225  	}
   226  
   227  	v, err := strconv.ParseInt(string(text[i:]), 10, 64)
   228  	if err != nil {
   229  		return errors.Wrapf(err, "invalid comparison value")
   230  	}
   231  
   232  	*exp = ComparisonExpr{Op: op, Value: v}
   233  	return nil
   234  }
   235  
   236  func (pred *ModifiedLines) Evaluate(ctx context.Context, prctx pull.Context) (*common.PredicateResult, error) {
   237  	files, err := prctx.ChangedFiles()
   238  
   239  	predicateResult := common.PredicateResult{
   240  		ValuePhrase:     "file modifications",
   241  		ConditionPhrase: "meet the modification conditions",
   242  	}
   243  
   244  	if err != nil {
   245  		return nil, errors.Wrap(err, "failed to list changed files")
   246  	}
   247  
   248  	var additions, deletions int64
   249  	for _, f := range files {
   250  		additions += int64(f.Additions)
   251  		deletions += int64(f.Deletions)
   252  	}
   253  
   254  	if !pred.Additions.IsEmpty() {
   255  		predicateResult.Values = []string{fmt.Sprintf("+%d", additions)}
   256  		predicateResult.ConditionValues = []string{fmt.Sprintf("added lines %s", pred.Additions.String())}
   257  		if pred.Additions.Evaluate(additions) {
   258  			predicateResult.Satisfied = true
   259  			return &predicateResult, nil
   260  		}
   261  	}
   262  	if !pred.Deletions.IsEmpty() {
   263  		if pred.Deletions.Evaluate(deletions) {
   264  			predicateResult.Values = []string{fmt.Sprintf("-%d", deletions)}
   265  			predicateResult.ConditionValues = []string{fmt.Sprintf("deleted lines %s", pred.Deletions.String())}
   266  			predicateResult.Satisfied = true
   267  			return &predicateResult, nil
   268  		}
   269  		predicateResult.Values = append(predicateResult.Values, fmt.Sprintf("-%d", deletions))
   270  		predicateResult.ConditionValues = append(predicateResult.ConditionValues, fmt.Sprintf("deleted lines %s", pred.Deletions.String()))
   271  	}
   272  	if !pred.Total.IsEmpty() {
   273  		if pred.Total.Evaluate(additions + deletions) {
   274  			predicateResult.Values = []string{fmt.Sprintf("total %d", additions+deletions)}
   275  			predicateResult.ConditionValues = []string{fmt.Sprintf("total modifications %s", pred.Total.String())}
   276  			predicateResult.Satisfied = true
   277  			return &predicateResult, nil
   278  		}
   279  		predicateResult.Values = append(predicateResult.Values, fmt.Sprintf("total %d", additions+deletions))
   280  		predicateResult.ConditionValues = append(predicateResult.ConditionValues, fmt.Sprintf("total modifications %s", pred.Total.String()))
   281  	}
   282  	predicateResult.Satisfied = false
   283  	return &predicateResult, nil
   284  }
   285  
   286  func (pred *ModifiedLines) Trigger() common.Trigger {
   287  	return common.TriggerCommit
   288  }
   289  
   290  var _ Predicate = &ModifiedLines{}
   291  

View as plain text