github.com/twelho/conform@v0.0.0-20231016230407-c25e9238598a/internal/enforcer/enforcer.go (about) 1 // This Source Code Form is subject to the terms of the Mozilla Public 2 // License, v. 2.0. If a copy of the MPL was not distributed with this 3 // file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5 // Package enforcer defines policy enforcement. 6 package enforcer 7 8 import ( 9 "fmt" 10 "log" 11 "os" 12 "text/tabwriter" 13 14 "github.com/mitchellh/mapstructure" 15 "github.com/pkg/errors" 16 yaml "gopkg.in/yaml.v2" 17 18 "github.com/twelho/conform/internal/policy" 19 "github.com/twelho/conform/internal/policy/commit" 20 "github.com/twelho/conform/internal/policy/license" 21 "github.com/twelho/conform/internal/reporter" 22 ) 23 24 // Conform is a struct that conform.yaml gets decoded into. 25 // 26 //nolint:govet 27 type Conform struct { 28 Policies []*PolicyDeclaration `yaml:"policies"` 29 reporter reporter.Reporter 30 } 31 32 // PolicyDeclaration allows a user to declare an arbitrary type along with a 33 // spec that will be decoded into the appropriate concrete type. 34 // 35 //nolint:govet 36 type PolicyDeclaration struct { 37 Type string `yaml:"type"` 38 Spec interface{} `yaml:"spec"` 39 } 40 41 // policyMap defines the set of policies allowed within Conform. 42 var policyMap = map[string]policy.Policy{ 43 "commit": &commit.Commit{}, 44 "license": &license.License{}, 45 // "version": &version.Version{}, 46 } 47 48 // New loads the conform.yaml file and unmarshals it into a Conform struct. 49 func New(r string) (*Conform, error) { 50 c := &Conform{} 51 52 switch r { 53 case "github": 54 s, err := reporter.NewGitHubReporter() 55 if err != nil { 56 return nil, err 57 } 58 59 c.reporter = s 60 default: 61 c.reporter = &reporter.Noop{} 62 } 63 64 configBytes, err := os.ReadFile(".conform.yaml") 65 if err != nil { 66 return nil, err 67 } 68 69 err = yaml.Unmarshal(configBytes, c) 70 if err != nil { 71 return nil, err 72 } 73 74 return c, nil 75 } 76 77 // Enforce enforces all policies defined in the conform.yaml file. 78 func (c *Conform) Enforce(setters ...policy.Option) error { 79 opts := policy.NewDefaultOptions(setters...) 80 81 const padding = 8 82 w := tabwriter.NewWriter(os.Stdout, 0, 0, padding, ' ', 0) 83 fmt.Fprintln(w, "POLICY\tCHECK\tSTATUS\tMESSAGE\t") 84 85 pass := true 86 87 for _, p := range c.Policies { 88 report, err := c.enforce(p, opts) 89 if err != nil { 90 log.Fatal(err) 91 } 92 93 for _, check := range report.Checks() { 94 if len(check.Errors()) != 0 { 95 for _, err := range check.Errors() { 96 fmt.Fprintf(w, "%s\t%s\t%s\t%v\t\n", p.Type, check.Name(), "FAILED", err) 97 } 98 99 if err := c.reporter.SetStatus("failure", p.Type, check.Name(), check.Message()); err != nil { 100 log.Printf("WARNING: report failed: %+v", err) 101 } 102 103 pass = false 104 } else { 105 fmt.Fprintf(w, "%s\t%s\t%s\t%s\t\n", p.Type, check.Name(), "PASS", check.Message()) 106 107 if err := c.reporter.SetStatus("success", p.Type, check.Name(), check.Message()); err != nil { 108 log.Printf("WARNING: report failed: %+v", err) 109 } 110 } 111 } 112 } 113 114 w.Flush() //nolint:errcheck 115 116 if !pass { 117 return errors.New("1 or more policy failed") 118 } 119 120 return nil 121 } 122 123 func (c *Conform) enforce(declaration *PolicyDeclaration, opts *policy.Options) (*policy.Report, error) { 124 if _, ok := policyMap[declaration.Type]; !ok { 125 return nil, errors.Errorf("Policy %q is not defined", declaration.Type) 126 } 127 128 p := policyMap[declaration.Type] 129 130 // backwards compatibility, convert `gpg: bool` into `gpg: required: bool` 131 if declaration.Type == "commit" { 132 if spec, ok := declaration.Spec.(map[interface{}]interface{}); ok { 133 if gpg, ok := spec["gpg"]; ok { 134 if val, ok := gpg.(bool); ok { 135 spec["gpg"] = map[string]interface{}{ 136 "required": val, 137 } 138 } 139 } 140 } 141 } 142 143 err := mapstructure.Decode(declaration.Spec, p) 144 if err != nil { 145 return nil, errors.Errorf("Internal error: %v", err) 146 } 147 148 return p.Compliance(opts) 149 }