Skip to content

Commit 90972e3

Browse files
authored
Detect service labels for PRs based on changed files (#10248)
1 parent f27c89d commit 90972e3

File tree

5 files changed

+338
-72
lines changed

5 files changed

+338
-72
lines changed

.ci/magician/cmd/DIFF_COMMENT.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Your PR hasn't generated any diffs, but I'll let you know if a future commit doe
77
Your PR generated some diffs in downstreams - here they are.
88

99
{{range .Diffs -}}
10-
{{.Title}}: [Diff](https://github.com/modular-magician/{{.Repo}}/compare/auto-pr-{{$.PrNumber}}-old..auto-pr-{{$.PrNumber}}) ({{.DiffStats}})
10+
{{.Title}}: [Diff](https://github.com/modular-magician/{{.Repo}}/compare/auto-pr-{{$.PrNumber}}-old..auto-pr-{{$.PrNumber}}) ({{.ShortStat}})
1111
{{end -}}
1212
{{end -}}
1313

.ci/magician/cmd/generate_comment.go

+104-51
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ import (
2020
"fmt"
2121
"os"
2222
"path/filepath"
23+
"regexp"
2324
"sort"
2425
"strconv"
2526
"strings"
2627
"text/template"
2728

29+
"github.com/GoogleCloudPlatform/magic-modules/tools/issue-labeler/labeler"
2830
"magician/exec"
2931
"magician/github"
3032
"magician/provider"
@@ -44,7 +46,7 @@ var (
4446
type Diff struct {
4547
Title string
4648
Repo string
47-
DiffStats string
49+
ShortStat string
4850
}
4951

5052
type Errors struct {
@@ -187,31 +189,41 @@ func execGenerateComment(prNumber int, ghTokenMagicModules, buildId, buildStep,
187189
for _, repo := range []*source.Repo{&tpgRepo, &tpgbRepo, &tgcRepo, &tfoicsRepo} {
188190
errors[repo.Title] = []string{}
189191
repo.Branch = newBranch
192+
repo.Cloned = true
190193
if err := ctlr.Clone(repo); err != nil {
191-
fmt.Println("Failed to clone repo: ", err)
192-
errors[repo.Title] = append(errors[repo.Title], "Failed to clone repo")
193-
} else {
194-
repo.Cloned = true
194+
fmt.Println("Failed to clone repo at new branch: ", err)
195+
errors[repo.Title] = append(errors[repo.Title], "Failed to clone repo at new branch")
196+
repo.Cloned = false
197+
}
198+
if err := ctlr.Fetch(repo, oldBranch); err != nil {
199+
fmt.Println("Failed to fetch old branch: ", err)
200+
errors[repo.Title] = append(errors[repo.Title], "Failed to clone repo at old branch")
201+
repo.Cloned = false
195202
}
196203
}
197204

198205
diffs := []Diff{}
199-
for _, repo := range []source.Repo{tpgRepo, tpgbRepo, tgcRepo, tfoicsRepo} {
206+
for _, repo := range []*source.Repo{&tpgRepo, &tpgbRepo, &tgcRepo, &tfoicsRepo} {
200207
if !repo.Cloned {
201208
fmt.Println("Skipping diff; repo failed to clone: ", repo.Name)
202209
continue
203210
}
204-
diffStats, err := computeDiff(&repo, oldBranch, ctlr)
211+
shortStat, err := ctlr.DiffShortStat(repo, oldBranch, newBranch)
205212
if err != nil {
206-
fmt.Println("diffing repo: ", err)
207-
errors[repo.Title] = append(errors[repo.Title], "Failed to compute repo diff stats")
213+
fmt.Println("Failed to compute repo diff --shortstat: ", err)
214+
errors[repo.Title] = append(errors[repo.Title], "Failed to compute repo diff shortstats")
208215
}
209-
if diffStats != "" {
216+
if shortStat != "" {
210217
diffs = append(diffs, Diff{
211218
Title: repo.Title,
212219
Repo: repo.Name,
213-
DiffStats: diffStats,
220+
ShortStat: shortStat,
214221
})
222+
repo.ChangedFiles, err = ctlr.DiffNameOnly(repo, oldBranch, newBranch)
223+
if err != nil {
224+
fmt.Println("Failed to compute repo diff --name-only: ", err)
225+
errors[repo.Title] = append(errors[repo.Title], "Failed to compute repo changed filenames")
226+
}
215227
}
216228
}
217229
data.Diffs = diffs
@@ -230,7 +242,11 @@ func execGenerateComment(prNumber int, ghTokenMagicModules, buildId, buildStep,
230242
}
231243
for _, repo := range []source.Repo{tpgRepo, tpgbRepo} {
232244
if !repo.Cloned {
233-
fmt.Println("Skipping breaking changes; repo failed to clone: ", repo.Name)
245+
fmt.Println("Skipping diff processor; repo failed to clone: ", repo.Name)
246+
continue
247+
}
248+
if len(repo.ChangedFiles) == 0 {
249+
fmt.Println("Skipping diff processor; no diff: ", repo.Name)
234250
continue
235251
}
236252
err = buildDiffProcessor(diffProcessorPath, repo.Path, diffProcessorEnv, rnr)
@@ -267,6 +283,33 @@ func execGenerateComment(prNumber int, ghTokenMagicModules, buildId, buildStep,
267283
sort.Strings(breakingChangesSlice)
268284
data.BreakingChanges = breakingChangesSlice
269285

286+
// Compute affected resources based on changed files
287+
affectedResources := map[string]struct{}{}
288+
for _, repo := range []source.Repo{tpgRepo, tpgbRepo} {
289+
if !repo.Cloned {
290+
fmt.Println("Skipping changed file service labels; repo failed to clone: ", repo.Name)
291+
continue
292+
}
293+
for _, path := range repo.ChangedFiles {
294+
if r := fileToResource(path); r != "" {
295+
affectedResources[r] = struct{}{}
296+
}
297+
}
298+
}
299+
fmt.Printf("affected resources based on changed files: %v\n", maps.Keys(affectedResources))
300+
301+
// Compute additional service labels based on affected resources
302+
regexpLabels, err := labeler.BuildRegexLabels(labeler.EnrolledTeamsYaml)
303+
if err != nil {
304+
fmt.Println("error building regexp labels: ", err)
305+
errors["Other"] = append(errors["Other"], "Failed to parse service label mapping")
306+
}
307+
if len(regexpLabels) > 0 {
308+
for _, label := range labeler.ComputeLabels(maps.Keys(affectedResources), regexpLabels) {
309+
uniqueServiceLabels[label] = struct{}{}
310+
}
311+
}
312+
270313
// Add service labels to PR
271314
if len(uniqueServiceLabels) > 0 {
272315
serviceLabelsSlice := maps.Keys(uniqueServiceLabels)
@@ -310,18 +353,21 @@ func execGenerateComment(prNumber int, ghTokenMagicModules, buildId, buildStep,
310353
data.MissingTests = missingTests
311354
}
312355

313-
// Run unit tests for missing test detector
314-
if err = runMissingTestUnitTests(
315-
mmLocalPath,
316-
tpgbRepo.Path,
317-
targetURL,
318-
commitSha,
319-
prNumber,
320-
gh,
321-
rnr,
322-
); err != nil {
323-
fmt.Println("Error running missing test detector unit tests: ", err)
324-
errors["Other"] = append(errors["Other"], "Missing test detector unit tests failed to run.")
356+
// Run unit tests for missing test detector (currently only for beta)
357+
if pathChanged("tools/missing-test-detector", tpgbRepo.ChangedFiles) {
358+
fmt.Printf("Found diffs in missing test detector:\n%s\nRunning tests.\n", diffs)
359+
if err = runMissingTestUnitTests(
360+
mmLocalPath,
361+
tpgbRepo.Path,
362+
targetURL,
363+
commitSha,
364+
prNumber,
365+
gh,
366+
rnr,
367+
); err != nil {
368+
fmt.Println("Error running missing test detector unit tests: ", err)
369+
errors["Other"] = append(errors["Other"], "Missing test detector unit tests failed to run.")
370+
}
325371
}
326372

327373
// Add errors to data as an ordered list
@@ -356,18 +402,6 @@ func execGenerateComment(prNumber int, ghTokenMagicModules, buildId, buildStep,
356402
}
357403
}
358404

359-
func computeDiff(repo *source.Repo, oldBranch string, ctlr *source.Controller) (string, error) {
360-
if err := ctlr.Fetch(repo, oldBranch); err != nil {
361-
return "", err
362-
}
363-
// Get shortstat summary of the diff
364-
diff, err := ctlr.Diff(repo, oldBranch, repo.Branch)
365-
if err != nil {
366-
return "", err
367-
}
368-
return strings.TrimSuffix(diff, "\n"), nil
369-
}
370-
371405
// Build the diff processor for tpg or tpgb
372406
func buildDiffProcessor(diffProcessorPath, providerLocalPath string, env map[string]string, rnr ExecRunner) error {
373407
for _, path := range []string{"old", "new", "bin"} {
@@ -515,21 +549,6 @@ func updatePackageName(name, path string, rnr ExecRunner) error {
515549
// Run unit tests for the missing test detector.
516550
// Report results using Github API.
517551
func runMissingTestUnitTests(mmLocalPath, tpgbLocalPath, targetURL, commitSha string, prNumber int, gh GithubClient, rnr ExecRunner) error {
518-
if err := rnr.PushDir(mmLocalPath); err != nil {
519-
return err
520-
}
521-
522-
diffs, err := rnr.Run("git", []string{"diff", "HEAD", "origin/main", "tools/missing-test-detector"}, nil)
523-
if err != nil {
524-
return err
525-
}
526-
if diffs == "" {
527-
// Short-circuit if there are no changes to the missing test detector
528-
return rnr.PopDir()
529-
}
530-
531-
fmt.Printf("Found diffs in missing test detector:\n%s\nRunning tests.\n", diffs)
532-
533552
missingTestDetectorPath := filepath.Join(mmLocalPath, "tools", "missing-test-detector")
534553
rnr.PushDir(missingTestDetectorPath)
535554
if _, err := rnr.Run("go", []string{"mod", "tidy"}, nil); err != nil {
@@ -565,6 +584,40 @@ func formatDiffComment(data diffCommentData) (string, error) {
565584
return sb.String(), nil
566585
}
567586

587+
var resourceFileRegexp = regexp.MustCompile(`^.*/services/[^/]+/(?:data_source_|resource_|iam_)(.*?)(?:_test|_sweeper|_iam_test|_generated_test|_internal_test)?.go`)
588+
var resourceDocsRegexp = regexp.MustCompile(`^.*website/docs/(?:r|d)/(.*).html.markdown`)
589+
590+
func fileToResource(path string) string {
591+
var submatches []string
592+
if strings.HasSuffix(path, ".go") {
593+
submatches = resourceFileRegexp.FindStringSubmatch(path)
594+
} else if strings.HasSuffix(path, ".html.markdown") {
595+
submatches = resourceDocsRegexp.FindStringSubmatch(path)
596+
}
597+
598+
if len(submatches) == 0 {
599+
return ""
600+
}
601+
602+
// The regexes will each return the resource name as the first
603+
// submatch, stripping any prefixes or suffixes.
604+
resource := submatches[1]
605+
606+
if !strings.HasPrefix(resource, "google_") {
607+
resource = "google_" + resource
608+
}
609+
return resource
610+
}
611+
612+
func pathChanged(path string, changedFiles []string) bool {
613+
for _, f := range changedFiles {
614+
if strings.HasPrefix(f, path) {
615+
return true
616+
}
617+
}
618+
return false
619+
}
620+
568621
func init() {
569622
rootCmd.AddCommand(generateCommentCmd)
570623
}

0 commit comments

Comments
 (0)