diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 482a2e357410..b9a1950fa4f2 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,6 +12,11 @@ updates: schedule: interval: "weekly" day: "wednesday" + - package-ecosystem: "gomod" + directory: "/cmd/githubgen" + schedule: + interval: "weekly" + day: "wednesday" - package-ecosystem: "gomod" directory: "/cmd/mdatagen" schedule: @@ -1097,8 +1102,3 @@ updates: schedule: interval: "weekly" day: "wednesday" - - package-ecosystem: "gomod" - directory: "/receiver/splunkenterprisereceiver" - schedule: - interval: "weekly" - day: "wednesday" diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index f4e852345764..44653249317e 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -208,10 +208,17 @@ jobs: run: | make -j2 generate git diff --exit-code ':!*go.sum' || (echo 'Generated code is out of date, please run "make generate" and commit the changes in this PR.' && exit 1) - - name: Gen codeowners + - uses: dorny/paths-filter@v2 + id: codeowner-changes + with: + filters: | + src: + - '**/metadata.yaml' + - name: Check codeowners + if: steps.codeowner-changes.outputs.src == 'true' run: | - make gengithub - git diff -s --exit-code || (echo 'Generated code is out of date, please run "make gengithub" and commit the changes in this PR.' && exit 1) + GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} make gengithub + git diff -s --exit-code || (echo 'Generated code or members.txt are out of date, please run "make gengithubcheck" and commit the changes in this PR.' && exit 1) - name: Check gendependabot run: | make -j2 gendependabot diff --git a/Makefile b/Makefile index a443dd0fd479..cc0d2ad281df 100644 --- a/Makefile +++ b/Makefile @@ -255,7 +255,8 @@ mdatagen-test: .PHONY: gengithub gengithub: - $(GOCMD) run cmd/githubgen/main.go . + cd cmd/githubgen && $(GOCMD) install . + githubgen .PHONY: update-codeowners update-codeowners: gengithub generate diff --git a/cmd/githubgen/Makefile b/cmd/githubgen/Makefile new file mode 100644 index 000000000000..ded7a36092dc --- /dev/null +++ b/cmd/githubgen/Makefile @@ -0,0 +1 @@ +include ../../Makefile.Common diff --git a/cmd/githubgen/README.md b/cmd/githubgen/README.md new file mode 100644 index 000000000000..2703499eba8d --- /dev/null +++ b/cmd/githubgen/README.md @@ -0,0 +1,31 @@ +# githubgen + +This executable is used to generate the `.github/CODEOWNERS` and `.github/ALLOWLIST` files. + +It reads status metadata from `metadata.yaml` files located throughout the repository. + +It checks that codeowners are known members of the OpenTelemetry organization. + +## Usage + +``` +$> make gengithub +``` +The equivalent of: +``` +$> cd cmd/githubgen && $(GOCMD) install . +$> GITHUB_TOKEN= githubgen --folder . [--allowlist cmd/githubgen/allowlist.txt] +``` + +## Checking codeowners against OpenTelemetry membership via Github API + +To authenticate, set the environment variable `GITHUB_TOKEN` to a PAT token. + +For each codeowner, the script will check if the user is registered as a member of the OpenTelemetry organization. + +If any codeowner is missing, it will stop and print names of missing codeowners. + +These can be added to allowlist.txt as a workaround. + +If a codeowner is present in allowlist.txt and also a member of the OpenTelemetry organization, the script will error out. + diff --git a/cmd/githubgen/allowlist.txt b/cmd/githubgen/allowlist.txt new file mode 100644 index 000000000000..e46daa181df1 --- /dev/null +++ b/cmd/githubgen/allowlist.txt @@ -0,0 +1,23 @@ +Caleb-Hurshman +Doron-Bargo +MaxKsyunz +MitchellGale +YANG-DB +agoallikmaa +alexvanboxel +architjugran +asaharn +avadhut123pisal +billmeyer +eedorenko +emreyalvac +keep94 +kiranmayib +kkujawa-sumo +leonsp-ai +liqiangz +oded-dd +shaochengwang +svrakitin +thepeterstone +yiyang5055 \ No newline at end of file diff --git a/cmd/githubgen/go.mod b/cmd/githubgen/go.mod new file mode 100644 index 000000000000..658c59193674 --- /dev/null +++ b/cmd/githubgen/go.mod @@ -0,0 +1,30 @@ +module github.com/open-telemetry/opentelemetry-collector-contrib/cmd/githubgen + +go 1.20 + +require ( + github.com/google/go-github/v53 v53.2.0 + go.opentelemetry.io/collector/confmap v0.83.0 +) + +require ( + github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect + github.com/cloudflare/circl v1.3.3 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/knadh/koanf/maps v0.1.1 // indirect + github.com/knadh/koanf/providers/confmap v0.1.0 // indirect + github.com/knadh/koanf/v2 v2.0.1 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + go.opentelemetry.io/collector/featuregate v1.0.0-rcv0014 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.7.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect + golang.org/x/sys v0.8.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/cmd/githubgen/go.sum b/cmd/githubgen/go.sum new file mode 100644 index 000000000000..0b22a3d3c488 --- /dev/null +++ b/cmd/githubgen/go.sum @@ -0,0 +1,73 @@ +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= +github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-github/v53 v53.2.0 h1:wvz3FyF53v4BK+AsnvCmeNhf8AkTaeh2SoYu/XUvTtI= +github.com/google/go-github/v53 v53.2.0/go.mod h1:XhFRObz+m/l+UCm9b7KSIC3lT3NWSXGt7mOsAWEloao= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= +github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= +github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU= +github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU= +github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g= +github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +go.opentelemetry.io/collector/confmap v0.83.0 h1:eUaiFdhTLkFdNpMi5FLSHSQ6X2FcEHe0KfEUt9ZtVlI= +go.opentelemetry.io/collector/confmap v0.83.0/go.mod h1:ZsmLyJ+4VeO+qz5o1RKadRoY4Db+d8PYwiLCJ3Z5Et8= +go.opentelemetry.io/collector/featuregate v1.0.0-rcv0014 h1:C9o0mbP0MyygqFnKueVQK/v9jef6zvuttmTGlKaqhgw= +go.opentelemetry.io/collector/featuregate v1.0.0-rcv0014/go.mod h1:0mE3mDLmUrOXVoNsuvj+7dV14h/9HFl/Fy9YTLoLObo= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/cmd/githubgen/main.go b/cmd/githubgen/main.go index d5fab2a24520..7d7bb41beef8 100644 --- a/cmd/githubgen/main.go +++ b/cmd/githubgen/main.go @@ -14,6 +14,7 @@ import ( "sort" "strings" + "github.com/google/go-github/v53/github" "go.opentelemetry.io/collector/confmap/provider/fileprovider" ) @@ -79,9 +80,10 @@ const unmaintainedStatus = "unmaintained" // .github/CODEOWNERS // .github/ALLOWLIST func main() { + folder := flag.String("folder", ".", "folder investigated for codeowners") + allowlistFilePath := flag.String("allowlist", "cmd/githubgen/allowlist.txt", "path to a file containing an allowlist of members outside the OpenTelemetry organization") flag.Parse() - folder := flag.Arg(0) - if err := run(folder); err != nil { + if err := run(*folder, *allowlistFilePath); err != nil { log.Fatal(err) } } @@ -127,11 +129,27 @@ func loadMetadata(filePath string) (metadata, error) { return md, nil } -func run(folder string) error { +func run(folder string, allowlistFilePath string) error { + members, err := getGithubMembers() + if err != nil { + return err + } + allowlistData, err := os.ReadFile(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{}{} + } + components := map[string]metadata{} - foldersList := []string{} + var foldersList []string maxLength := 0 - err := filepath.Walk(folder, func(path string, info fs.FileInfo, err error) error { + allCodeowners := map[string]struct{}{} + err = filepath.Walk(folder, func(path string, info fs.FileInfo, err error) error { if info.Name() == "metadata.yaml" { m, err := loadMetadata(path) if err != nil { @@ -149,16 +167,43 @@ func run(folder string) error { return nil } } + for _, id := range m.Status.Codeowners.Active { + allCodeowners[id] = struct{}{} + } if len(key) > maxLength { maxLength = len(key) } } return nil }) - sort.Strings(foldersList) if err != nil { return err } + sort.Strings(foldersList) + var missingCodeowners []string + var duplicateCodeowners []string + for codeowner := range allCodeowners { + _, 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" @@ -206,3 +251,35 @@ LOOP: return nil } + +func getGithubMembers() (map[string]struct{}, error) { + client := github.NewTokenClient(context.Background(), os.Getenv("GITHUB_TOKEN")) + 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 +} diff --git a/go.mod b/go.mod index 3bf8a5959f25..74a8a74a2759 100644 --- a/go.mod +++ b/go.mod @@ -168,7 +168,6 @@ require ( github.com/open-telemetry/opentelemetry-collector-contrib/receiver/zipkinreceiver v0.83.0 github.com/open-telemetry/opentelemetry-collector-contrib/receiver/zookeeperreceiver v0.83.0 go.opentelemetry.io/collector v0.83.0 - go.opentelemetry.io/collector/confmap v0.83.0 go.opentelemetry.io/collector/exporter v0.83.0 go.opentelemetry.io/collector/exporter/loggingexporter v0.83.0 go.opentelemetry.io/collector/exporter/otlpexporter v0.83.0 @@ -627,6 +626,7 @@ require ( go.opentelemetry.io/collector/config/configtelemetry v0.83.0 // indirect go.opentelemetry.io/collector/config/configtls v0.83.0 // indirect go.opentelemetry.io/collector/config/internal v0.83.0 // indirect + go.opentelemetry.io/collector/confmap v0.83.0 // indirect go.opentelemetry.io/collector/connector v0.83.0 // indirect go.opentelemetry.io/collector/consumer v0.83.0 // indirect go.opentelemetry.io/collector/extension/auth v0.83.0 // indirect diff --git a/versions.yaml b/versions.yaml index ed400774e236..894e5ed24550 100644 --- a/versions.yaml +++ b/versions.yaml @@ -7,6 +7,7 @@ module-sets: modules: - github.com/open-telemetry/opentelemetry-collector-contrib - github.com/open-telemetry/opentelemetry-collector-contrib/cmd/configschema + - github.com/open-telemetry/opentelemetry-collector-contrib/cmd/githubgen - github.com/open-telemetry/opentelemetry-collector-contrib/cmd/mdatagen - github.com/open-telemetry/opentelemetry-collector-contrib/cmd/opampsupervisor - github.com/open-telemetry/opentelemetry-collector-contrib/cmd/telemetrygen