...

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

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

     1  package owners
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  	"strings"
     7  
     8  	"edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/policy"
     9  	"edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/policy/approval"
    10  	"edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/policy/common"
    11  	"edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/policy/disapproval"
    12  	"edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/policy/predicate"
    13  	"edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/pull"
    14  )
    15  
    16  // GeneratePolicyBotConfig generates a single policy-bot.yaml for the repository
    17  // from a map of OWNERS file contents and their directories. It sets opinionated
    18  // defaults for some rules options:
    19  //
    20  // - Ignore commits by our instance of bulldozer
    21  // - Ignore update commits
    22  // - Respect GitHub reviews as an approval method
    23  // - Disable GitHub comments as approval method
    24  // - Invalidate review on push
    25  //
    26  // And opinionated defaults for the policy config itself:
    27  //
    28  //   - Enables disapproval, allowing write collaborators on the repo to disapprove
    29  //     PRs via GitHub review. This is because policy-bot only allows one disapproval
    30  //     rule per config.
    31  //
    32  // TODO(aw185176): this could be made more generic by accepting an approval.Options argument and using reflect to merge it with the rules being read.
    33  func GeneratePolicyBotConfig(keys []string, ofiles map[string]File, rootOrRules []*approval.Rule) (*policy.Config, error) {
    34  	ghReview := true
    35  
    36  	p := &policy.Config{
    37  		ApprovalRules: []*approval.Rule{},
    38  		Policy: policy.Policy{
    39  			Disapproval: &disapproval.Policy{
    40  				Options: disapproval.Options{
    41  					Methods: disapproval.Methods{Disapprove: &common.Methods{
    42  						GithubReview: &ghReview,
    43  					}},
    44  				},
    45  				Requires: disapproval.Requires{
    46  					Actors: common.Actors{
    47  						Permissions: []pull.Permission{pull.PermissionWrite},
    48  					},
    49  				},
    50  			},
    51  		},
    52  	}
    53  
    54  	// generate the approval rules defined by OWNERS files
    55  	genApproval := approval.Policy{}
    56  	for _, key := range keys {
    57  		v, ok := ofiles[key]
    58  		if !ok {
    59  			continue
    60  		}
    61  		for _, r := range v.Rules {
    62  			setCommonOptions(r)
    63  			// prefix all file paths with path of directory where OWNERS file was read
    64  			var err error
    65  			if r.Predicates.ChangedFiles != nil {
    66  				r.Predicates.ChangedFiles, err = prefixChangeFilePaths(
    67  					r.Predicates.ChangedFiles,
    68  					key,
    69  				)
    70  				if err != nil {
    71  					return nil, err
    72  				}
    73  			}
    74  
    75  			if r.Predicates.OnlyChangedFiles != nil {
    76  				r.Predicates.OnlyChangedFiles.Paths, err = prefixRegexp(
    77  					r.Predicates.OnlyChangedFiles.Paths,
    78  					key,
    79  				)
    80  				if err != nil {
    81  					return nil, err
    82  				}
    83  			}
    84  		}
    85  
    86  		p.ApprovalRules = append(p.ApprovalRules, v.Rules...)
    87  		genApproval = append(genApproval, v.Approval...)
    88  	}
    89  
    90  	// if no root OR policies are defined, set our generated policies at the root
    91  	// and return
    92  	if len(rootOrRules) == 0 {
    93  		p.Policy.Approval = genApproval
    94  		return p, nil
    95  	}
    96  
    97  	// if root OR policies are defined, nest the generated policies under it,
    98  	// ANDed together
    99  	rules := make([]interface{}, len(rootOrRules)+1)
   100  	for i, r := range rootOrRules {
   101  		setCommonOptions(r)
   102  		rules[i] = r.Name
   103  	}
   104  	rules[len(rootOrRules)] = map[interface{}]interface{}{"and": genApproval}
   105  
   106  	p.Policy.Approval = append(p.Policy.Approval, map[interface{}]interface{}{
   107  		"or": rules,
   108  	})
   109  	p.ApprovalRules = append(p.ApprovalRules, rootOrRules...)
   110  	return p, nil
   111  }
   112  
   113  func prefixChangeFilePaths(files *predicate.ChangedFiles, path string) (*predicate.ChangedFiles, error) {
   114  	result := &predicate.ChangedFiles{
   115  		Paths: make([]common.Regexp, len(files.Paths)),
   116  	}
   117  
   118  	var err error
   119  	result.Paths, err = prefixRegexp(files.Paths, path)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	if len(files.IgnorePaths) == 0 {
   125  		return result, nil
   126  	}
   127  
   128  	result.IgnorePaths = make([]common.Regexp, len(files.IgnorePaths))
   129  	result.IgnorePaths, err = prefixRegexp(files.IgnorePaths, path)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  
   134  	return result, nil
   135  }
   136  
   137  func prefixRegexp(rr []common.Regexp, path string) ([]common.Regexp, error) {
   138  	for i, r := range rr {
   139  		var (
   140  			rstr    = r.String()
   141  			pattern string
   142  		)
   143  
   144  		switch {
   145  		case strings.HasPrefix(rstr, "^"):
   146  			pattern = fmt.Sprintf(
   147  				"^%s", filepath.Join(path, strings.TrimPrefix(rstr, "^")),
   148  			)
   149  		default:
   150  			pattern = filepath.Join(path, rstr)
   151  		}
   152  
   153  		re, err := common.NewRegexp(pattern)
   154  		if err != nil {
   155  			return nil, err
   156  		}
   157  		rr[i] = re
   158  	}
   159  	return rr, nil
   160  }
   161  
   162  func setCommonOptions(r *approval.Rule) {
   163  	ghReview := true
   164  	r.Options.InvalidateOnPush = true
   165  	r.Options.IgnoreUpdateMerges = true
   166  	r.Options.IgnoreCommitsBy = common.Actors{
   167  		Users: []string{"edge-bulldozer[bot]"},
   168  	}
   169  	r.Options.Methods = &common.Methods{
   170  		GithubReview: &ghReview,
   171  		Comments:     []string{},
   172  	}
   173  }
   174  

View as plain text