main.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. // Copyright The OpenTelemetry Authors
  2. // SPDX-License-Identifier: Apache-2.0
  3. package main
  4. import (
  5. "context"
  6. "flag"
  7. "fmt"
  8. "io/fs"
  9. "log"
  10. "path/filepath"
  11. "sort"
  12. "go.opentelemetry.io/collector/confmap/provider/fileprovider"
  13. )
  14. const unmaintainedStatus = "unmaintained"
  15. type generator interface {
  16. generate(data *githubData) error
  17. }
  18. // Generates files specific to Github according to status metadata:
  19. // .github/CODEOWNERS
  20. // .github/ALLOWLIST
  21. // .github/ISSUE_TEMPLATES/*.yaml (list of components)
  22. func main() {
  23. folder := flag.String("folder", ".", "folder investigated for codeowners")
  24. allowlistFilePath := flag.String("allowlist", "cmd/githubgen/allowlist.txt", "path to a file containing an allowlist of members outside the OpenTelemetry organization")
  25. flag.Parse()
  26. var generators []generator
  27. for _, arg := range flag.Args() {
  28. switch arg {
  29. case "issue-templates":
  30. generators = append(generators, issueTemplatesGenerator{})
  31. case "codeowners":
  32. generators = append(generators, codeownersGenerator{})
  33. default:
  34. panic(fmt.Sprintf("Unknown generator: %s", arg))
  35. }
  36. }
  37. if len(generators) == 0 {
  38. generators = []generator{issueTemplatesGenerator{}, codeownersGenerator{}}
  39. }
  40. if err := run(*folder, *allowlistFilePath, generators); err != nil {
  41. log.Fatal(err)
  42. }
  43. }
  44. type codeowners struct {
  45. // Active codeowners
  46. Active []string `mapstructure:"active"`
  47. // Emeritus codeowners
  48. Emeritus []string `mapstructure:"emeritus"`
  49. }
  50. type Status struct {
  51. Stability map[string][]string `mapstructure:"stability"`
  52. Distributions []string `mapstructure:"distributions"`
  53. Class string `mapstructure:"class"`
  54. Warnings []string `mapstructure:"warnings"`
  55. Codeowners *codeowners `mapstructure:"codeowners"`
  56. }
  57. type metadata struct {
  58. // Type of the component.
  59. Type string `mapstructure:"type"`
  60. // Type of the parent component (applicable to subcomponents).
  61. Parent string `mapstructure:"parent"`
  62. // Status information for the component.
  63. Status *Status `mapstructure:"status"`
  64. }
  65. type githubData struct {
  66. folders []string
  67. codeowners []string
  68. allowlistFilePath string
  69. maxLength int
  70. components map[string]metadata
  71. }
  72. func loadMetadata(filePath string) (metadata, error) {
  73. cp, err := fileprovider.New().Retrieve(context.Background(), "file:"+filePath, nil)
  74. if err != nil {
  75. return metadata{}, err
  76. }
  77. conf, err := cp.AsConf()
  78. if err != nil {
  79. return metadata{}, err
  80. }
  81. md := metadata{}
  82. if err := conf.Unmarshal(&md); err != nil {
  83. return md, err
  84. }
  85. return md, nil
  86. }
  87. func run(folder string, allowlistFilePath string, generators []generator) error {
  88. components := map[string]metadata{}
  89. var foldersList []string
  90. maxLength := 0
  91. allCodeowners := map[string]struct{}{}
  92. err := filepath.Walk(folder, func(path string, info fs.FileInfo, err error) error {
  93. if info.Name() == "metadata.yaml" {
  94. m, err := loadMetadata(path)
  95. if err != nil {
  96. return err
  97. }
  98. if m.Status == nil {
  99. return nil
  100. }
  101. currentFolder := filepath.Dir(path)
  102. key := currentFolder
  103. components[key] = m
  104. foldersList = append(foldersList, key)
  105. for stability := range m.Status.Stability {
  106. if stability == unmaintainedStatus {
  107. // do not account for unmaintained status to change the max length of the component line.
  108. return nil
  109. }
  110. }
  111. if m.Status == nil || m.Status.Codeowners == nil {
  112. return fmt.Errorf("component %q has no status or codeowners section", key)
  113. }
  114. for _, id := range m.Status.Codeowners.Active {
  115. allCodeowners[id] = struct{}{}
  116. }
  117. if len(key) > maxLength {
  118. maxLength = len(key)
  119. }
  120. }
  121. return nil
  122. })
  123. if err != nil {
  124. return err
  125. }
  126. sort.Strings(foldersList)
  127. codeownersList := make([]string, 0, len(allCodeowners))
  128. for c := range allCodeowners {
  129. codeownersList = append(codeownersList, c)
  130. }
  131. sort.Strings(codeownersList)
  132. data := &githubData{
  133. folders: foldersList,
  134. codeowners: codeownersList,
  135. allowlistFilePath: allowlistFilePath,
  136. maxLength: maxLength,
  137. components: components,
  138. }
  139. for _, g := range generators {
  140. if err = g.generate(data); err != nil {
  141. return err
  142. }
  143. }
  144. return nil
  145. }