codeowners.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. // Copyright The OpenTelemetry Authors
  2. // SPDX-License-Identifier: Apache-2.0
  3. package main
  4. import (
  5. "context"
  6. "fmt"
  7. "os"
  8. "path/filepath"
  9. "sort"
  10. "strings"
  11. "github.com/google/go-github/v53/github"
  12. )
  13. const allowlistHeader = `# Code generated by githubgen. DO NOT EDIT.
  14. #####################################################
  15. #
  16. # List of components in OpenTelemetry Collector Contrib
  17. # waiting on owners to be assigned
  18. #
  19. #####################################################
  20. #
  21. # Learn about membership in OpenTelemetry community:
  22. # https://github.com/open-telemetry/community/blob/main/community-membership.md
  23. #
  24. #
  25. # Learn about CODEOWNERS file format:
  26. # https://help.github.com/en/articles/about-code-owners
  27. #
  28. ##
  29. # NOTE: New components MUST have a codeowner. Add new components to the CODEOWNERS file instead of here.
  30. ##
  31. ## COMMON & SHARED components
  32. internal/common
  33. `
  34. const unmaintainedHeader = `
  35. ## UNMAINTAINED components
  36. ## The Github issue template generation code needs this to generate the corresponding labels.
  37. `
  38. const codeownersHeader = `# Code generated by githubgen. DO NOT EDIT.
  39. #####################################################
  40. #
  41. # List of approvers for OpenTelemetry Collector Contrib
  42. #
  43. #####################################################
  44. #
  45. # Learn about membership in OpenTelemetry community:
  46. # https://github.com/open-telemetry/community/blob/main/community-membership.md
  47. #
  48. #
  49. # Learn about CODEOWNERS file format:
  50. # https://help.github.com/en/articles/about-code-owners
  51. #
  52. # NOTE: Lines should be entered in the following format:
  53. # <component_path_relative_from_project_root>/<min_1_space><owner_1><space><owner_2><space>..<owner_n>
  54. # extension/oauth2clientauthextension/ @open-telemetry/collector-contrib-approvers @pavankrish123 @jpkrohling
  55. # Path separator and minimum of 1 space between component path and owners is
  56. # important for validation steps
  57. #
  58. * @open-telemetry/collector-contrib-approvers
  59. `
  60. type codeownersGenerator struct {
  61. }
  62. func (cg codeownersGenerator) generate(data *githubData) error {
  63. allowlistData, err := os.ReadFile(data.allowlistFilePath)
  64. if err != nil {
  65. return err
  66. }
  67. allowlistLines := strings.Split(string(allowlistData), "\n")
  68. allowlist := make(map[string]struct{}, len(allowlistLines))
  69. for _, line := range allowlistLines {
  70. allowlist[line] = struct{}{}
  71. }
  72. var missingCodeowners []string
  73. var duplicateCodeowners []string
  74. members, err := getGithubMembers()
  75. if err != nil {
  76. return err
  77. }
  78. for _, codeowner := range data.codeowners {
  79. _, present := members[codeowner]
  80. if !present {
  81. _, allowed := allowlist[codeowner]
  82. allowed = allowed || strings.HasPrefix(codeowner, "open-telemetry/")
  83. if !allowed {
  84. missingCodeowners = append(missingCodeowners, codeowner)
  85. }
  86. } else if _, ok := allowlist[codeowner]; ok {
  87. duplicateCodeowners = append(duplicateCodeowners, codeowner)
  88. }
  89. }
  90. if len(missingCodeowners) > 0 {
  91. sort.Strings(missingCodeowners)
  92. return fmt.Errorf("codeowners are not members: %s", strings.Join(missingCodeowners, ", "))
  93. }
  94. if len(duplicateCodeowners) > 0 {
  95. sort.Strings(duplicateCodeowners)
  96. return fmt.Errorf("codeowners members duplicate in allowlist: %s", strings.Join(duplicateCodeowners, ", "))
  97. }
  98. codeowners := codeownersHeader
  99. deprecatedList := "## DEPRECATED components\n"
  100. unmaintainedList := "\n## UNMAINTAINED components\n"
  101. unmaintainedCodeowners := unmaintainedHeader
  102. currentFirstSegment := ""
  103. LOOP:
  104. for _, key := range data.folders {
  105. m := data.components[key]
  106. for stability := range m.Status.Stability {
  107. if stability == unmaintainedStatus {
  108. unmaintainedList += key + "/\n"
  109. unmaintainedCodeowners += fmt.Sprintf("%s/%s @open-telemetry/collector-contrib-approvers \n", key, strings.Repeat(" ", data.maxLength-len(key)))
  110. continue LOOP
  111. }
  112. if stability == "deprecated" && (m.Status.Codeowners == nil || len(m.Status.Codeowners.Active) == 0) {
  113. deprecatedList += key + "/\n"
  114. }
  115. }
  116. if m.Status.Codeowners != nil {
  117. parts := strings.Split(key, string(os.PathSeparator))
  118. firstSegment := parts[0]
  119. if firstSegment != currentFirstSegment {
  120. currentFirstSegment = firstSegment
  121. codeowners += "\n"
  122. }
  123. owners := ""
  124. for _, owner := range m.Status.Codeowners.Active {
  125. owners += " "
  126. owners += "@" + owner
  127. }
  128. codeowners += fmt.Sprintf("%s/%s @open-telemetry/collector-contrib-approvers%s\n", key, strings.Repeat(" ", data.maxLength-len(key)), owners)
  129. }
  130. }
  131. err = os.WriteFile(filepath.Join(".github", "CODEOWNERS"), []byte(codeowners+unmaintainedCodeowners), 0600)
  132. if err != nil {
  133. return err
  134. }
  135. err = os.WriteFile(filepath.Join(".github", "ALLOWLIST"), []byte(allowlistHeader+deprecatedList+unmaintainedList), 0600)
  136. if err != nil {
  137. return err
  138. }
  139. return nil
  140. }
  141. func getGithubMembers() (map[string]struct{}, error) {
  142. githubToken := os.Getenv("GITHUB_TOKEN")
  143. if githubToken == "" {
  144. return nil, fmt.Errorf("Set the environment variable `GITHUB_TOKEN` to a PAT token to authenticate")
  145. }
  146. client := github.NewTokenClient(context.Background(), githubToken)
  147. var allUsers []*github.User
  148. pageIndex := 0
  149. for {
  150. users, resp, err := client.Organizations.ListMembers(context.Background(), "open-telemetry",
  151. &github.ListMembersOptions{
  152. PublicOnly: false,
  153. ListOptions: github.ListOptions{
  154. PerPage: 50,
  155. Page: pageIndex,
  156. },
  157. },
  158. )
  159. if err != nil {
  160. return nil, err
  161. }
  162. defer resp.Body.Close()
  163. if len(users) == 0 {
  164. break
  165. }
  166. allUsers = append(allUsers, users...)
  167. pageIndex++
  168. }
  169. usernames := make(map[string]struct{}, len(allUsers))
  170. for _, u := range allUsers {
  171. usernames[*u.Login] = struct{}{}
  172. }
  173. return usernames, nil
  174. }