Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Add a script for creating labels #2778

Merged
merged 6 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion pkg/scripts/issues/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,22 @@
6. To close the issues with the appropriate comment provide `issues_to_close.csv` in `close-with-comment` dir. Example `20240430 - issues_to_close.csv` is given. The run:
```shell
cd close-with-comment && SF_TF_SCRIPT_GH_ACCESS_TOKEN=<YOUR_PERSONAL_ACCESS_TOKEN> go run .
```
```

# Creating new labels and assigning them to issues
1. Firstly, make sure all the needed labels exist in the repository, by running:
```shell
cd create-labels && SF_TF_SCRIPT_GH_ACCESS_TOKEN=<YOUR_PERSONAL_ACCESS_TOKEN> go run .
```
2. Then, we have to get data about the existing issues with:
```shell
cd gh && SF_TF_SCRIPT_GH_ACCESS_TOKEN=<YOUR_PERSONAL_ACCESS_TOKEN> go run .
```
3. Afterward, we need to process `issues.json` with:
```shell
cd file && go run .
```
sfc-gh-asawicki marked this conversation as resolved.
Show resolved Hide resolved
4. Next you have to analyze generated CSV and assign categories in the `Category` column and resource / data source in the `Object` column (the `GitHub issues buckets` Excel should be used here named as `GitHubIssuesBucket.csv`). The csv document be of a certain format with the following columns (with headers): "A" column with issue ID (in the format of "#<issue_id>"), "P" column with the category that should be assigned to the issue (should be one of the supported categories: "OTHER", "RESOURCE", "DATA_SOURCE", "IMPORT", "SDK", "IDENTIFIERS", "PROVIDER_CONFIG", "GRANTS", and "DOCUMENTATION"), and the "Q" column with the object type (should be in the format of the terraform resource, e.g. "snowflake_database"). Then, you'll be able to use this csv (put it next to the `main.go`) to assign labels to the correct issues.
```shell
cd assign-labels && SF_TF_SCRIPT_GH_ACCESS_TOKEN=<YOUR_PERSONAL_ACCESS_TOKEN> go run .
```
180 changes: 180 additions & 0 deletions pkg/scripts/issues/assign-labels/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package main

import (
"bytes"
"encoding/csv"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"os"
"strconv"
"strings"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/scripts/issues"
)

var lookupTable = make(map[string]string)

func init() {
for _, label := range issues.RepositoryLabels {
parts := strings.Split(label, ":")
if len(parts) != 2 {
panic(fmt.Sprintf("invalid label: %s", label))
}

labelType := parts[0]
sfc-gh-asawicki marked this conversation as resolved.
Show resolved Hide resolved
labelValue := parts[1]

switch labelType {
case "category":
lookupTable[strings.ToUpper(labelValue)] = label
case "resource", "data_source":
lookupTable[fmt.Sprintf("snowflake_%s", labelValue)] = label
}
}
}

func main() {
accessToken := getAccessToken()
githubIssuesBucket := readGitHubIssuesBucket()
successful, failed := assignLabelsToIssues(accessToken, githubIssuesBucket)
fmt.Printf("\nSuccessfully assigned labels to issues:\n")
for _, assignResult := range successful {
fmt.Println(assignResult.IssueId, assignResult.Labels)
}
fmt.Printf("\nUnsuccessful to assign labels to issues:\n")
for _, assignResult := range failed {
fmt.Println(assignResult.IssueId, assignResult.Labels)
}
}

type AssignResult struct {
IssueId int
Labels []string
}

type Issue struct {
ID int `json:"id"`
Category string `json:"category"`
Object string `json:"object"`
}

func readGitHubIssuesBucket() []Issue {
f, err := os.Open("GitHubIssuesBucket.csv")
if err != nil {
panic(err)
}
defer f.Close()
csvReader := csv.NewReader(f)
records, err := csvReader.ReadAll()
if err != nil {
panic(err)
}
issues := make([]Issue, 0)
for _, record := range records[1:] { // Skip header
id, err := strconv.Atoi(record[14])
if err != nil {
panic(err)
}
issues = append(issues, Issue{
ID: id,
Category: record[15],
Object: record[16],
})
}
return issues
}

func assignLabelsToIssues(accessToken string, issues []Issue) (successful []AssignResult, failed []AssignResult) {
for _, issue := range issues {
addLabelsRequestBody := createAddLabelsRequestBody(issue)
if addLabelsRequestBody == nil {
log.Println("couldn't create add label request body from issue", issue)
failed = append(failed, AssignResult{
IssueId: issue.ID,
})
continue
}

addLabelsRequestBodyBytes, err := json.Marshal(addLabelsRequestBody)
if err != nil {
log.Println("failed to marshal add label request:", err)
failed = append(failed, AssignResult{
IssueId: issue.ID,
Labels: addLabelsRequestBody.Labels,
})
continue
}

req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("https://api.github.com/repos/Snowflake-Labs/terraform-provider-snowflake/issues/%d/labels", issue.ID), bytes.NewReader(addLabelsRequestBodyBytes))
if err != nil {
log.Println("failed to create add label request:", err)
failed = append(failed, AssignResult{
IssueId: issue.ID,
Labels: addLabelsRequestBody.Labels,
})
continue
}
req.Header.Set("Accept", "application/vnd.github+json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
req.Header.Set("X-GitHub-Api-Version", "2022-11-28")

resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Println("failed to add a new labels:", err)
failed = append(failed, AssignResult{
IssueId: issue.ID,
Labels: addLabelsRequestBody.Labels,
})
continue
}

if resp.StatusCode != http.StatusOK {
log.Println("incorrect status code, expected 200, and got:", resp.StatusCode)
failed = append(failed, AssignResult{
IssueId: issue.ID,
Labels: addLabelsRequestBody.Labels,
})
continue
}

successful = append(successful, AssignResult{
IssueId: issue.ID,
Labels: addLabelsRequestBody.Labels,
})
}

return successful, failed
}

type AddLabelsRequestBody struct {
Labels []string `json:"labels"`
}

func createAddLabelsRequestBody(issue Issue) *AddLabelsRequestBody {
if categoryLabel, ok := lookupTable[issue.Category]; ok {
if issue.Category == "RESOURCE" || issue.Category == "DATA_SOURCE" {
if resourceName, ok := lookupTable[issue.Object]; ok {
return &AddLabelsRequestBody{
Labels: []string{categoryLabel, resourceName},
}
}
}

return &AddLabelsRequestBody{
Labels: []string{categoryLabel},
}
}

return nil
}

func getAccessToken() string {
token := os.Getenv("SF_TF_SCRIPT_GH_ACCESS_TOKEN")
if token == "" {
panic(errors.New("GitHub access token missing"))
}
return token
}
151 changes: 151 additions & 0 deletions pkg/scripts/issues/create-labels/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package main

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"slices"
"strings"
"time"
sfc-gh-asawicki marked this conversation as resolved.
Show resolved Hide resolved

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/scripts/issues"
)

func main() {
accessToken := getAccessToken()
repoLabels := loadRepoLabels(accessToken)
jsonRepoLabels, _ := json.MarshalIndent(repoLabels, "", "\t")
log.Println(string(jsonRepoLabels))
successful, failed := createLabelsIfNotPresent(accessToken, repoLabels, issues.RepositoryLabels)
fmt.Printf("\nSuccessfully created labels:\n")
for _, label := range successful {
fmt.Println(label)
}
fmt.Printf("\nUnsuccessful label creation:\n")
for _, label := range failed {
fmt.Println(label)
}
}

type ReadLabel struct {
ID int `json:"id"`
NodeId string `json:"node_id"`
URL string `json:"url"`
Name string `json:"name"`
Description string `json:"description"`
Color string `json:"color"`
Default bool `json:"default"`
}

func loadRepoLabels(accessToken string) []ReadLabel {
req, err := http.NewRequest(http.MethodGet, "https://api.github.com/repos/Snowflake-Labs/terraform-provider-snowflake/labels", nil)
if err != nil {
panic("failed to create list labels request: " + err.Error())
}
req.Header.Set("Accept", "application/vnd.github+json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
req.Header.Set("X-GitHub-Api-Version", "2022-11-28")

resp, err := http.DefaultClient.Do(req)
if err != nil {
panic("failed to retrieve repository labels: " + err.Error())
}

bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
panic("failed to read list labels response body: " + err.Error())
}

var readLabels []ReadLabel
err = json.Unmarshal(bodyBytes, &readLabels)
if err != nil {
panic("failed to unmarshal read labels: " + err.Error())
}

return readLabels
}

type CreateLabelRequestBody struct {
Name string `json:"name"`
Description string `json:"description"`
}

func createLabelsIfNotPresent(accessToken string, repoLabels []ReadLabel, labels []string) (successful []string, failed []string) {
repoLabelNames := make([]string, len(repoLabels))
for i, label := range repoLabels {
repoLabelNames[i] = label.Name
}

for _, label := range labels {
if slices.Contains(repoLabelNames, label) {
continue
}

time.Sleep(3 * time.Second)
log.Println("Processing:", label)

var requestBody []byte
var err error
parts := strings.Split(label, ":")
labelType := parts[0]
labelValue := parts[1]

switch labelType {
// Categories will be created by hand
case "resource", "data_source":
requestBody, err = json.Marshal(&CreateLabelRequestBody{
Name: label,
Description: fmt.Sprintf("Issue connected to the snowflake_%s resource", labelValue),
})
default:
log.Println("Unknown label type:", labelType)
continue
}

if err != nil {
log.Println("Failed to marshal create label request body:", err)
failed = append(failed, label)
continue
}

req, err := http.NewRequest(http.MethodPost, "https://api.github.com/repos/Snowflake-Labs/terraform-provider-snowflake/labels", bytes.NewReader(requestBody))
if err != nil {
log.Println("failed to create label request:", err)
failed = append(failed, label)
continue
}
req.Header.Set("Accept", "application/vnd.github+json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
req.Header.Set("X-GitHub-Api-Version", "2022-11-28")

resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Println("failed to create a new label: ", label, err)
failed = append(failed, label)
continue
}

if resp.StatusCode != http.StatusCreated {
log.Println("incorrect status code, expected 201, and got:", resp.StatusCode)
failed = append(failed, label)
continue
}

successful = append(successful, label)
}

return successful, failed
}

func getAccessToken() string {
token := os.Getenv("SF_TF_SCRIPT_GH_ACCESS_TOKEN")
if token == "" {
panic(errors.New("GitHub access token missing"))
}
return token
}
Loading
Loading