Skip to content

Commit

Permalink
[chore] generate issue templates with githubgen (#28655)
Browse files Browse the repository at this point in the history
Remove a bash script, use go code instead.

---------

Co-authored-by: Andrzej Stencel <[email protected]>
  • Loading branch information
atoulme and andrzej-stencel authored Nov 21, 2023
1 parent fbd61ae commit 4ea95dc
Show file tree
Hide file tree
Showing 6 changed files with 430 additions and 344 deletions.
51 changes: 0 additions & 51 deletions .github/workflows/scripts/add-component-options.sh

This file was deleted.

11 changes: 3 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ DEPENDABOT_PATH=".github/dependabot.yml"
.PHONY: gendependabot
gendependabot:
cd cmd/githubgen && $(GOCMD) install .
githubgen -dependabot
githubgen dependabot


# Define a delegation target for each module
Expand Down Expand Up @@ -398,10 +398,5 @@ genconfigdocs:

.PHONY: generate-gh-issue-templates
generate-gh-issue-templates:
for FILE in bug_report feature_request other; do \
YAML_FILE=".github/ISSUE_TEMPLATE/$${FILE}.yaml"; \
TMP_FILE=".github/ISSUE_TEMPLATE/$${FILE}.yaml.tmp"; \
cat "$${YAML_FILE}" > "$${TMP_FILE}"; \
FILE="$${TMP_FILE}" ./.github/workflows/scripts/add-component-options.sh > "$${YAML_FILE}"; \
rm "$${TMP_FILE}"; \
done
cd cmd/githubgen && $(GOCMD) install .
githubgen issue-templates
195 changes: 195 additions & 0 deletions cmd/githubgen/codeowners.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package main

import (
"context"
"fmt"
"os"
"path/filepath"
"sort"
"strings"

"github.com/google/go-github/v53/github"
)

const allowlistHeader = `# Code generated by githubgen. DO NOT EDIT.
#####################################################
#
# List of components in OpenTelemetry Collector Contrib
# waiting on owners to be assigned
#
#####################################################
#
# Learn about membership in OpenTelemetry community:
# https://github.com/open-telemetry/community/blob/main/community-membership.md
#
#
# Learn about CODEOWNERS file format:
# https://help.github.com/en/articles/about-code-owners
#
##
# NOTE: New components MUST have a codeowner. Add new components to the CODEOWNERS file instead of here.
##
## COMMON & SHARED components
internal/common
`

const unmaintainedHeader = `
## UNMAINTAINED components
## The Github issue template generation code needs this to generate the corresponding labels.
`

const codeownersHeader = `# Code generated by githubgen. DO NOT EDIT.
#####################################################
#
# List of approvers for OpenTelemetry Collector Contrib
#
#####################################################
#
# Learn about membership in OpenTelemetry community:
# https://github.com/open-telemetry/community/blob/main/community-membership.md
#
#
# Learn about CODEOWNERS file format:
# https://help.github.com/en/articles/about-code-owners
#
# NOTE: Lines should be entered in the following format:
# <component_path_relative_from_project_root>/<min_1_space><owner_1><space><owner_2><space>..<owner_n>
# extension/oauth2clientauthextension/ @open-telemetry/collector-contrib-approvers @pavankrish123 @jpkrohling
# Path separator and minimum of 1 space between component path and owners is
# important for validation steps
#
* @open-telemetry/collector-contrib-approvers
`

type codeownersGenerator struct {
}

func (cg codeownersGenerator) generate(data *githubData) error {
allowlistData, err := os.ReadFile(data.allowlistFilePath)
if err != nil {
return err
}
allowlistLines := strings.Split(string(allowlistData), "\n")

allowlist := make(map[string]struct{}, len(allowlistLines))
for _, line := range allowlistLines {
allowlist[line] = struct{}{}
}
var missingCodeowners []string
var duplicateCodeowners []string
members, err := getGithubMembers()
if err != nil {
return err
}
for _, codeowner := range data.codeowners {
_, present := members[codeowner]

if !present {
_, allowed := allowlist[codeowner]
allowed = allowed || strings.HasPrefix(codeowner, "open-telemetry/")
if !allowed {
missingCodeowners = append(missingCodeowners, codeowner)
}
} else if _, ok := allowlist[codeowner]; ok {
duplicateCodeowners = append(duplicateCodeowners, codeowner)
}
}
if len(missingCodeowners) > 0 {
sort.Strings(missingCodeowners)
return fmt.Errorf("codeowners are not members: %s", strings.Join(missingCodeowners, ", "))
}
if len(duplicateCodeowners) > 0 {
sort.Strings(duplicateCodeowners)
return fmt.Errorf("codeowners members duplicate in allowlist: %s", strings.Join(duplicateCodeowners, ", "))
}

codeowners := codeownersHeader
deprecatedList := "## DEPRECATED components\n"
unmaintainedList := "\n## UNMAINTAINED components\n"

unmaintainedCodeowners := unmaintainedHeader
currentFirstSegment := ""
LOOP:
for _, key := range data.folders {
m := data.components[key]
for stability := range m.Status.Stability {
if stability == unmaintainedStatus {
unmaintainedList += key + "/\n"
unmaintainedCodeowners += fmt.Sprintf("%s/%s @open-telemetry/collector-contrib-approvers \n", key, strings.Repeat(" ", data.maxLength-len(key)))
continue LOOP
}
if stability == "deprecated" && (m.Status.Codeowners == nil || len(m.Status.Codeowners.Active) == 0) {
deprecatedList += key + "/\n"
}
}

if m.Status.Codeowners != nil {
parts := strings.Split(key, string(os.PathSeparator))
firstSegment := parts[0]
if firstSegment != currentFirstSegment {
currentFirstSegment = firstSegment
codeowners += "\n"
}
owners := ""
for _, owner := range m.Status.Codeowners.Active {
owners += " "
owners += "@" + owner
}
codeowners += fmt.Sprintf("%s/%s @open-telemetry/collector-contrib-approvers%s\n", key, strings.Repeat(" ", data.maxLength-len(key)), owners)
}
}

err = os.WriteFile(filepath.Join(".github", "CODEOWNERS"), []byte(codeowners+unmaintainedCodeowners), 0600)
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(".github", "ALLOWLIST"), []byte(allowlistHeader+deprecatedList+unmaintainedList), 0600)
if err != nil {
return err
}
return nil
}

func getGithubMembers() (map[string]struct{}, error) {
githubToken := os.Getenv("GITHUB_TOKEN")
if githubToken == "" {
return nil, fmt.Errorf("Set the environment variable `GITHUB_TOKEN` to a PAT token to authenticate")
}
client := github.NewTokenClient(context.Background(), githubToken)
var allUsers []*github.User
pageIndex := 0
for {
users, resp, err := client.Organizations.ListMembers(context.Background(), "open-telemetry",
&github.ListMembersOptions{
PublicOnly: false,
ListOptions: github.ListOptions{
PerPage: 50,
Page: pageIndex,
},
},
)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if len(users) == 0 {
break
}
allUsers = append(allUsers, users...)
pageIndex++
}

usernames := make(map[string]struct{}, len(allUsers))
for _, u := range allUsers {
usernames[*u.Login] = struct{}{}
}
return usernames, nil
}
115 changes: 115 additions & 0 deletions cmd/githubgen/dependabot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package main

import (
"bytes"
"fmt"
"os"
"path/filepath"
"sort"
"strings"

"gopkg.in/yaml.v3"
)

const (
maxDependabotUpdates = 220
dependabotHeader = `# File generated by "make gendependabot"; DO NOT EDIT.
`
)

type dependabotSchedule struct {
Interval yaml.Node `yaml:"interval"`
Day yaml.Node `yaml:"day"`
}

type dependabotUpdate struct {
PackageEcosystem yaml.Node `yaml:"package-ecosystem"`
Directory yaml.Node `yaml:"directory"`
Schedule dependabotSchedule `yaml:"schedule"`
Priority int `yaml:"-"`
}

type dependabotData struct {
Version int `yaml:"version"`
Updates []dependabotUpdate `yaml:"updates"`
}

func makePriority(status *Status) int {
// not an internal component such as pkg/**, and no distributions:
if (status.Class == "receiver" || status.Class == "processor" || status.Class == "exporter" || status.Class == "connector" || status.Class == "extension" || status.Class == "cmd") &&
len(status.Distributions) == 0 && status.Class != "" {
return 1
}
// start with a score of 2
maxScore := 2
for stability := range status.Stability {
switch stability {
case "deprecated": // stay with 2
case "unmaintained":
return 1
case "alpha":
if maxScore < 3 {
maxScore = 3
}
case "beta":
if maxScore < 4 {
maxScore = 4
}
case "stable":
if maxScore < 5 {
maxScore = 5
}
}
}
return maxScore
}

func newDependabotUpdate(directory string, priority int) dependabotUpdate {
return dependabotUpdate{
PackageEcosystem: yaml.Node{Value: "gomod", Style: yaml.DoubleQuotedStyle, Kind: yaml.ScalarNode},
Directory: yaml.Node{Value: "/" + directory, Style: yaml.DoubleQuotedStyle, Kind: yaml.ScalarNode},
Schedule: dependabotSchedule{
Interval: yaml.Node{Value: "weekly", Style: yaml.DoubleQuotedStyle, Kind: yaml.ScalarNode},
Day: yaml.Node{Value: "wednesday", Style: yaml.DoubleQuotedStyle, Kind: yaml.ScalarNode},
},
Priority: priority,
}
}

type dependabotGenerator struct {
}

func (dg dependabotGenerator) generate(data *githubData) error {
dependabotData := data.dependabotData
sort.Slice(dependabotData.Updates, func(i, j int) bool {
return dependabotData.Updates[i].Priority > dependabotData.Updates[j].Priority
})
removed := dependabotData.Updates[maxDependabotUpdates:]
dependabotData.Updates = dependabotData.Updates[:maxDependabotUpdates]
if len(removed) > 0 {
sort.Slice(removed, func(i, j int) bool {
return strings.Compare(removed[i].Directory.Value, removed[j].Directory.Value) < 0
})
fmt.Printf("The following modules were not added to Dependabot to keep within limits of %d\n", maxDependabotUpdates)
for _, update := range removed {
fmt.Printf(" - %q\n", update.Directory.Value)
}
}

sort.Slice(dependabotData.Updates, func(i, j int) bool {
return strings.Compare(dependabotData.Updates[i].Directory.Value, dependabotData.Updates[j].Directory.Value) < 0
})

var yamlContents bytes.Buffer
encoder := yaml.NewEncoder(&yamlContents)
encoder.SetIndent(2)
err := encoder.Encode(dependabotData)
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(".github", "dependabot.yml"), append([]byte(dependabotHeader), yamlContents.Bytes()...), 0600)
return err
}
Loading

0 comments on commit 4ea95dc

Please sign in to comment.