Skip to content

Commit

Permalink
Converted issue-labeler tool to use cobra (#12473)
Browse files Browse the repository at this point in the history
  • Loading branch information
melinath authored Dec 4, 2024
1 parent 94d5485 commit 9af477c
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 55 deletions.
2 changes: 1 addition & 1 deletion .ci/magician/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ replace github.com/GoogleCloudPlatform/magic-modules/tools/issue-labeler => ../.
require (
github.com/GoogleCloudPlatform/magic-modules/tools/issue-labeler v0.0.0-00010101000000-000000000000
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/cobra v1.7.0
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/exp v0.0.0-20230810033253-352e893a4cad
google.golang.org/api v0.114.0
Expand Down
6 changes: 3 additions & 3 deletions .ci/magician/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
Expand Down Expand Up @@ -77,8 +77,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
70 changes: 70 additions & 0 deletions tools/issue-labeler/cmd/backfill_issue_labels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2024 Google LLC. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cmd

import (
"fmt"
"os"

"github.com/spf13/cobra"

"github.com/GoogleCloudPlatform/magic-modules/tools/issue-labeler/labeler"
)

var (
// used for flags
backfillSince string
backfillDryRun bool
)

var backfillIssueLabels = &cobra.Command{
Use: "backfill-issue-labels [--dry-run] [--since=1973-01-01]",
Short: "Backfills labels on old issues",
Long: "Backfills labels on old issues",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
// For now actual usage is handled inside UpdateIssues. This is just a new quick check.
_, ok := os.LookupEnv("GITHUB_TOKEN")
if !ok {
return fmt.Errorf("did not provide GITHUB_TOKEN environment variable")
}
return execBackfillIssueLabels()
},
}

func execBackfillIssueLabels() error {
regexpLabels, err := labeler.BuildRegexLabels(labeler.EnrolledTeamsYaml)
if err != nil {
return fmt.Errorf("building regex labels: %w", err)
}
repository := "hashicorp/terraform-provider-google"
issues, err := labeler.GetIssues(repository, backfillSince)
if err != nil {
return fmt.Errorf("getting github issues: %w", err)
}
issueUpdates := labeler.ComputeIssueUpdates(issues, regexpLabels)
err = labeler.UpdateIssues(repository, issueUpdates, backfillDryRun)
if err != nil {
return fmt.Errorf("updating github issues: %w", err)
}
return nil
}

func init() {
rootCmd.AddCommand(backfillIssueLabels)
backfillIssueLabels.Flags().BoolVar(&backfillDryRun, "dry-run", false, "Only log write actions instead of updating issues")
backfillIssueLabels.Flags().StringVar(&backfillSince, "since", "1973-01-01", "Only apply labels to issues filed after given date")
}
58 changes: 58 additions & 0 deletions tools/issue-labeler/cmd/compute_new_labels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2024 Google LLC. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cmd

import (
"fmt"
"os"
"sort"
"strings"

"github.com/spf13/cobra"

"github.com/GoogleCloudPlatform/magic-modules/tools/issue-labeler/labeler"
)

var computeNewLabels = &cobra.Command{
Use: "compute-new-labels",
Short: "Computes labels that should be added to an issue based on its body",
Long: "Computes labels that should be added to an issue based on its body",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return execComputeNewLabels()
},
}

func execComputeNewLabels() error {
regexpLabels, err := labeler.BuildRegexLabels(labeler.EnrolledTeamsYaml)
if err != nil {
return fmt.Errorf("building regex labels: %w", err)
}
issueBody := os.Getenv("ISSUE_BODY")
affectedResources := labeler.ExtractAffectedResources(issueBody)
labels := labeler.ComputeLabels(affectedResources, regexpLabels)

if len(labels) > 0 {
labels = append(labels, "forward/review")
sort.Strings(labels)
fmt.Println(`["` + strings.Join(labels, `", "`) + `"]`)
}
return nil
}

func init() {
rootCmd.AddCommand(computeNewLabels)
}
44 changes: 44 additions & 0 deletions tools/issue-labeler/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2024 Google LLC. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cmd

import (
"fmt"
"os"

"github.com/spf13/cobra"
)

// rootCmd represents the base command when called without any subcommands

var rootCmd = &cobra.Command{
Use: "issue-labeler",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args)
},
Short: "Tool for interacting with issue labels (specifically for services)",
Long: `Tool for interacting with issue labels (specifically for services)`,
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCmd.Execute()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
7 changes: 6 additions & 1 deletion tools/issue-labeler/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ require (
gopkg.in/yaml.v2 v2.4.0
)

require gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/cobra v1.8.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)
9 changes: 9 additions & 0 deletions tools/issue-labeler/go.sum
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/golang/glog v1.1.1 h1:jxpi2eWoU84wbX9iIEyAeeoac3FLuifZpY9tcNUD9kw=
github.com/golang/glog v1.1.1/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/exp v0.0.0-20230810033253-352e893a4cad h1:g0bG7Z4uG+OgH2QDODnjp6ggkk1bJDsINcuWmJN1iJU=
golang.org/x/exp v0.0.0-20230810033253-352e893a4cad/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
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/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
35 changes: 22 additions & 13 deletions tools/issue-labeler/labeler/backfill.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package labeler
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
Expand Down Expand Up @@ -37,7 +38,7 @@ type IssueUpdateBody struct {
Labels []string `json:"labels"`
}

func GetIssues(repository, since string) []Issue {
func GetIssues(repository, since string) ([]Issue, error) {
client := &http.Client{}
done := false
page := 1
Expand All @@ -46,27 +47,27 @@ func GetIssues(repository, since string) []Issue {
url := fmt.Sprintf("https://api.github.com/repos/%s/issues?since=%s&per_page=100&page=%d", repository, since, page)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
glog.Exitf("Error creating request: %v", err)
return nil, fmt.Errorf("creating request: %w", err)
}
req.Header.Add("Accept", "application/vnd.github+json")
req.Header.Add("Authorization", "Bearer "+os.Getenv("GITHUB_TOKEN"))
req.Header.Add("X-GitHub-Api-Version", "2022-11-28")
resp, err := client.Do(req)
if err != nil {
glog.Exitf("Error listing issues: %v", err)
return nil, fmt.Errorf("listing issues: %v", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
glog.Exitf("Error reading response body: %v", err)
return nil, fmt.Errorf("reading response body: %v", err)
}
var newIssues []Issue
json.Unmarshal(body, &newIssues)
if len(newIssues) == 0 {
var err ErrorResponse
json.Unmarshal(body, &err)
if err.Message == "Bad credentials" {
glog.Exitf("Error from API: Bad credentials")
return nil, errors.New("Error from API: Bad credentials")
}
glog.Infof("API returned message: %s", err.Message)
done = true
Expand All @@ -75,7 +76,7 @@ func GetIssues(repository, since string) []Issue {
page++
}
}
return issues
return issues, nil
}

func ComputeIssueUpdates(issues []Issue, regexpLabels []RegexpLabel) []IssueUpdate {
Expand Down Expand Up @@ -134,52 +135,60 @@ func ComputeIssueUpdates(issues []Issue, regexpLabels []RegexpLabel) []IssueUpda
return issueUpdates
}

func UpdateIssues(repository string, issueUpdates []IssueUpdate, dryRun bool) {
func UpdateIssues(repository string, issueUpdates []IssueUpdate, dryRun bool) error {
client := &http.Client{}
failed := 0
for _, issueUpdate := range issueUpdates {
url := fmt.Sprintf("https://api.github.com/repos/%s/issues/%d", repository, issueUpdate.Number)
updateBody := IssueUpdateBody{Labels: issueUpdate.Labels}
body, err := json.Marshal(updateBody)
if err != nil {
glog.Errorf("Error marshalling json: %v", err)
continue
return fmt.Errorf("marshalling json: %w", err)
}
buf := bytes.NewReader(body)
req, err := http.NewRequest("PATCH", url, buf)
req.Header.Add("Authorization", "Bearer "+os.Getenv("GITHUB_TOKEN"))
req.Header.Add("X-GitHub-Api-Version", "2022-11-28")
if err != nil {
glog.Errorf("Error creating request: %v", err)
continue
return fmt.Errorf("creating request: %w", err)
}
fmt.Printf("Existing labels: %v\n", issueUpdate.OldLabels)
fmt.Printf("New labels: %v\n", issueUpdate.Labels)
fmt.Printf("%s %s (https://github.com/%s/issues/%d)\n", req.Method, req.URL, repository, issueUpdate.Number)

// Pretty-print the body for debugging
b, err := json.MarshalIndent(updateBody, "", " ")
if err != nil {
glog.Errorf("Error marshalling json: %v", err)
continue
return fmt.Errorf("Error marshalling json: %w", err)
}
fmt.Println(string(b))

if !dryRun {
resp, err := client.Do(req)
if err != nil {
glog.Errorf("Error updating issue: %v", err)
failed += 1
continue
}
body, err := io.ReadAll(resp.Body)
if err != nil {
glog.Errorf("Error reading response body: %v", err)
failed += 1
continue
}
var errResp ErrorResponse
json.Unmarshal(body, &errResp)
if errResp.Message != "" {
fmt.Printf("API error: %s", errResp.Message)
failed += 1
continue
}

}
fmt.Printf("GitHub Issue %s %d updated successfully", repository, issueUpdate.Number)
}
if failed > 0 {
return fmt.Errorf("failed to update %d / %d issues", failed, len(issueUpdates))
}
return nil
}
2 changes: 1 addition & 1 deletion tools/issue-labeler/labeler/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func BuildRegexLabels(teamsYaml []byte) ([]RegexpLabel, error) {
enrolledTeams := make(map[string]LabelData)
regexpLabels := []RegexpLabel{}
if err := yaml.Unmarshal(teamsYaml, &enrolledTeams); err != nil {
return regexpLabels, fmt.Errorf("Error unmarshalling enrolled teams yaml: %w", err)
return regexpLabels, fmt.Errorf("unmarshalling enrolled teams yaml: %w", err)
}

for label, data := range enrolledTeams {
Expand Down
Loading

0 comments on commit 9af477c

Please sign in to comment.