cloudflare/pint

Public

mirrored from https://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.48.2

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

cmd/pint/ci.go

306lines · modecode

1package main
2
3import (
4 "context"
5 "fmt"
6 "log/slog"
7 "os"
8 "regexp"
9 "strconv"
10 "strings"
11 "time"
12
13 "github.com/cloudflare/pint/internal/checks"
14 "github.com/cloudflare/pint/internal/config"
15 "github.com/cloudflare/pint/internal/discovery"
16 "github.com/cloudflare/pint/internal/git"
17 "github.com/cloudflare/pint/internal/reporter"
18
19 "github.com/urfave/cli/v2"
20)
21
22var (
23 baseBranchFlag = "base-branch"
24 devFlag = "dev"
25 failOnFlag = "fail-on"
26)
27
28var ciCmd = &cli.Command{
29 Name: "ci",
30 Usage: "Lint CI changes",
31 Action: actionCI,
32 Flags: []cli.Flag{
33 &cli.BoolFlag{
34 Name: requireOwnerFlag,
35 Aliases: []string{"r"},
36 Value: false,
37 Usage: "Require all rules to have an owner set via comment",
38 },
39 &cli.StringFlag{
40 Name: baseBranchFlag,
41 Aliases: []string{"b"},
42 Value: "",
43 Usage: "Set base branch to use for PR checks (main, master, ...)",
44 },
45 &cli.BoolFlag{
46 Name: devFlag,
47 Aliases: []string{"n"},
48 Value: false,
49 Usage: "Use experimental change detection",
50 },
51 &cli.StringFlag{
52 Name: failOnFlag,
53 Aliases: []string{"w"},
54 Value: "bug",
55 Usage: "Exit with non-zero code if there are problems with given severity (or higher) detected",
56 },
57 },
58}
59
60func actionCI(c *cli.Context) error {
61 meta, err := actionSetup(c)
62 if err != nil {
63 return err
64 }
65
66 includeRe := []*regexp.Regexp{}
67 for _, pattern := range meta.cfg.CI.Include {
68 includeRe = append(includeRe, regexp.MustCompile("^"+pattern+"$"))
69 }
70
71 excludeRe := []*regexp.Regexp{}
72 for _, pattern := range meta.cfg.CI.Exclude {
73 excludeRe = append(excludeRe, regexp.MustCompile("^"+pattern+"$"))
74 }
75
76 meta.cfg.CI = detectCI(meta.cfg.CI)
77 baseBranch := meta.cfg.CI.BaseBranch
78 if c.String(baseBranchFlag) != "" {
79 baseBranch = c.String(baseBranchFlag)
80 }
81 currentBranch, err := git.CurrentBranch(git.RunGit)
82 if err != nil {
83 return fmt.Errorf("failed to get the name of current branch")
84 }
85 slog.Debug("Got branch information", slog.String("base", baseBranch), slog.String("current", currentBranch))
86 if currentBranch == strings.Split(baseBranch, "/")[len(strings.Split(baseBranch, "/"))-1] {
87 slog.Info("Running from base branch, skipping checks", slog.String("branch", currentBranch))
88 return nil
89 }
90
91 var entries []discovery.Entry
92 if c.Bool(devFlag) {
93 finder := discovery.NewGitBranchFinder(git.RunGit, includeRe, excludeRe, baseBranch, meta.cfg.CI.MaxCommits, meta.cfg.Parser.CompileRelaxed())
94 entries, err = finder.Find()
95 } else {
96 finder := discovery.NewGitBlameFinder(git.RunGit, includeRe, excludeRe, baseBranch, meta.cfg.CI.MaxCommits, meta.cfg.Parser.CompileRelaxed())
97 entries, err = finder.Find()
98 }
99 if err != nil {
100 return err
101 }
102
103 ctx := context.WithValue(context.Background(), config.CommandKey, config.CICommand)
104
105 gen := config.NewPrometheusGenerator(meta.cfg, metricsRegistry)
106 defer gen.Stop()
107
108 if err = gen.GenerateStatic(); err != nil {
109 return err
110 }
111
112 slog.Debug("Generated all Prometheus servers", slog.Int("count", gen.Count()))
113
114 summary, err := checkRules(ctx, meta.workers, gen, meta.cfg, entries)
115 if err != nil {
116 return err
117 }
118
119 if c.Bool(requireOwnerFlag) {
120 summary.Report(verifyOwners(entries, meta.cfg.Owners.CompileAllowed())...)
121 }
122
123 reps := []reporter.Reporter{
124 reporter.NewConsoleReporter(os.Stderr, checks.Information),
125 }
126
127 if meta.cfg.Repository != nil && meta.cfg.Repository.BitBucket != nil {
128 token, ok := os.LookupEnv("BITBUCKET_AUTH_TOKEN")
129 if !ok {
130 return fmt.Errorf("BITBUCKET_AUTH_TOKEN env variable is required when reporting to BitBucket")
131 }
132
133 timeout, _ := time.ParseDuration(meta.cfg.Repository.BitBucket.Timeout)
134 br := reporter.NewBitBucketReporter(
135 version,
136 meta.cfg.Repository.BitBucket.URI,
137 timeout,
138 token,
139 meta.cfg.Repository.BitBucket.Project,
140 meta.cfg.Repository.BitBucket.Repository,
141 git.RunGit,
142 )
143 reps = append(reps, br)
144 }
145
146 meta.cfg.Repository = detectRepository(meta.cfg.Repository)
147 if meta.cfg.Repository != nil && meta.cfg.Repository.GitHub != nil {
148 token, ok := os.LookupEnv("GITHUB_AUTH_TOKEN")
149 if !ok {
150 return fmt.Errorf("GITHUB_AUTH_TOKEN env variable is required when reporting to GitHub")
151 }
152
153 prVal, ok := os.LookupEnv("GITHUB_PULL_REQUEST_NUMBER")
154 if !ok {
155 return fmt.Errorf("GITHUB_PULL_REQUEST_NUMBER env variable is required when reporting to GitHub")
156 }
157
158 var prNum int
159 if prNum, err = strconv.Atoi(prVal); err != nil {
160 return fmt.Errorf("got not a valid number via GITHUB_PULL_REQUEST_NUMBER: %w", err)
161 }
162
163 timeout, _ := time.ParseDuration(meta.cfg.Repository.GitHub.Timeout)
164 var gr reporter.GithubReporter
165 if gr, err = reporter.NewGithubReporter(
166 version,
167 meta.cfg.Repository.GitHub.BaseURI,
168 meta.cfg.Repository.GitHub.UploadURI,
169 timeout,
170 token,
171 meta.cfg.Repository.GitHub.Owner,
172 meta.cfg.Repository.GitHub.Repo,
173 prNum,
174 git.RunGit,
175 ); err != nil {
176 return err
177 }
178 reps = append(reps, gr)
179 }
180
181 minSeverity, err := checks.ParseSeverity(c.String(failOnFlag))
182 if err != nil {
183 return fmt.Errorf("invalid --%s value: %w", failOnFlag, err)
184 }
185
186 problemsFound := false
187 bySeverity := summary.CountBySeverity()
188 for s := range bySeverity {
189 if s >= minSeverity {
190 problemsFound = true
191 break
192 }
193 }
194 if len(bySeverity) > 0 {
195 slog.Info("Problems found", logSeverityCounters(bySeverity)...)
196 }
197
198 if err := submitReports(reps, summary); err != nil {
199 return fmt.Errorf("submitting reports: %w", err)
200 }
201
202 if problemsFound {
203 return fmt.Errorf("problems found")
204 }
205
206 return nil
207}
208
209func logSeverityCounters(src map[checks.Severity]int) (attrs []any) {
210 for _, s := range []checks.Severity{checks.Fatal, checks.Bug, checks.Warning, checks.Information} {
211 if c, ok := src[s]; ok {
212 attrs = append(attrs, slog.Attr{Key: s.String(), Value: slog.IntValue(c)})
213 }
214 }
215 return attrs
216}
217
218func detectCI(cfg *config.CI) *config.CI {
219 var isNil, isDirty bool
220
221 if cfg == nil {
222 isNil = true
223 cfg = &config.CI{}
224 }
225
226 if bb := os.Getenv("GITHUB_BASE_REF"); bb != "" {
227 isDirty = true
228 cfg.BaseBranch = bb
229 slog.Debug("got base branch from GITHUB_BASE_REF env variable", slog.String("branch", bb))
230 }
231
232 if isNil && !isDirty {
233 return nil
234 }
235 return cfg
236}
237
238func detectRepository(cfg *config.Repository) *config.Repository {
239 var isNil, isDirty bool
240
241 if cfg == nil {
242 isNil = true
243 cfg = &config.Repository{}
244 }
245
246 if os.Getenv("GITHUB_ACTION") != "" {
247 isDirty = true
248 cfg.GitHub = detectGithubActions(cfg.GitHub)
249 }
250
251 if isNil && !isDirty {
252 return nil
253 }
254 return cfg
255}
256
257func detectGithubActions(gh *config.GitHub) *config.GitHub {
258 if os.Getenv("GITHUB_PULL_REQUEST_NUMBER") == "" &&
259 os.Getenv("GITHUB_EVENT_NAME") == "pull_request" &&
260 os.Getenv("GITHUB_REF") != "" {
261 parts := strings.Split(os.Getenv("GITHUB_REF"), "/")
262 if len(parts) >= 4 {
263 slog.Info("Setting GITHUB_PULL_REQUEST_NUMBER from GITHUB_REF env variable", slog.String("pr", parts[2]))
264 os.Setenv("GITHUB_PULL_REQUEST_NUMBER", parts[2])
265 }
266 }
267
268 var isDirty, isNil bool
269
270 if gh == nil {
271 isNil = true
272 gh = &config.GitHub{Timeout: time.Minute.String()}
273 }
274
275 if repo := os.Getenv("GITHUB_REPOSITORY"); repo != "" {
276 parts := strings.SplitN(repo, "/", 2)
277 if len(parts) == 2 {
278 if gh.Owner == "" {
279 slog.Info("Setting repository owner from GITHUB_REPOSITORY env variable", slog.String("owner", parts[0]))
280 gh.Owner = parts[0]
281 isDirty = true
282 }
283 if gh.Repo == "" {
284 slog.Info("Setting repository name from GITHUB_REPOSITORY env variable", slog.String("repo", parts[1]))
285 gh.Repo = parts[1]
286 isDirty = true
287 }
288 }
289 }
290
291 if api := os.Getenv("GITHUB_API_URL"); api != "" {
292 if gh.BaseURI == "" {
293 slog.Info("Setting repository base URI from GITHUB_API_URL env variable", slog.String("baseuri", api))
294 gh.BaseURI = api
295 }
296 if gh.UploadURI == "" {
297 slog.Info("Setting repository upload URI from GITHUB_API_URL env variable", slog.String("uploaduri", api))
298 gh.UploadURI = api
299 }
300 }
301
302 if isNil && !isDirty {
303 return nil
304 }
305 return gh
306}
307