diff --git a/pkg/scripts/issues/README.md b/pkg/scripts/issues/README.md index a3da4a028d..885b38360d 100644 --- a/pkg/scripts/issues/README.md +++ b/pkg/scripts/issues/README.md @@ -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= go run . -``` \ No newline at end of file +``` + +# 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= go run . +``` +2. Then, we have to get data about the existing issues with: +```shell + cd gh && SF_TF_SCRIPT_GH_ACCESS_TOKEN= go run . +``` +3. Afterward, we need to process `issues.json` with: +```shell + cd file && go run . +``` +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`; Update already existing one). The csv document be of a certain format with the following columns (with headers): "A" column with issue ID (in the format of "#"), "B" 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 "C" 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= go run . +``` diff --git a/pkg/scripts/issues/assign-labels/GitHubIssuesBucket.csv b/pkg/scripts/issues/assign-labels/GitHubIssuesBucket.csv new file mode 100644 index 0000000000..2b620812cc --- /dev/null +++ b/pkg/scripts/issues/assign-labels/GitHubIssuesBucket.csv @@ -0,0 +1,479 @@ +ID,Category,Object +#2280,PROVIDER_CONFIG,snowflake +#2277,RESOURCE,snowflake_database +#2276,RESOURCE,snowflake_dynamic_table +#2272,RESOURCE,snowflake_warehouse +#2271,RESOURCE,snowflake_share +#2263,RESOURCE,snowflake_user +#2257,RESOURCE,snowflake_procedure +#2249,RESOURCE,snowflake_iceberg_table +#2242,PROVIDER_CONFIG,snowflake +#2240,OTHER, +#2236,RESOURCE,snowflake_table_column_masking_policy_application +#2226,RESOURCE,snowflake_schema +#2223,RESOURCE,snowflake_email_notification_integration +#2218,DATA_SOURCE,snowflake_databases +#2213,RESOURCE,snowflake_password_policy +#2212,RESOURCE,snowflake_password_policy +#2211,RESOURCE,snowflake_schema +#2209,RESOURCE,snowflake_schema +#2208,PROVIDER_CONFIG, +#2207,RESOURCE,snowflake_task +#2201,RESOURCE,snowflake_stream +#2199,GRANTS,snowflake_sequence_grant +#2198,GRANTS,snowflake_role_ownership_grant +#2194,PROVIDER_CONFIG, +#2189,RESOURCE,snowflake_share +#2188,OTHER, +#2187,GRANTS,snowflake_grant_privileges_to_role +#2181,DOCUMENTATION,snowflake_system_get_aws_sns_iam_policy +#2177,RESOURCE,snowflake_oauth_integration +#2175,RESOURCE,snowflake_resource_monitor +#2169,PROVIDER_CONFIG, +#2168,IDENTIFIERS,snowflake_database +#2167,RESOURCE,snowflake_resource_monitor +#2165,RESOURCE,snowflake_pipe +#2164,IDENTIFIERS,snowflake_grant_privileges_to_role +#2162,RESOURCE,snowflake_password_policy_attachment +#2159,GRANTS,snowflake_grant_privileges_to_database_role +#2158,DATA_SOURCE,snowflake_accounts +#2154,RESOURCE,snowflake_file_format +#2151,GRANTS,snowflake_grant_privileges_to_role +#2146,RESOURCE,snowflake_procedure +#2145,PROVIDER_CONFIG, +#2137,PROVIDER_CONFIG, +#2120,RESOURCE,snowflake_stage +#2116,DATA_SOURCE,snowflake_functions +#2110,RESOURCE,snowflake_table +#2102,IDENTIFIERS,snowflake_grant_privileges_to_role +#2098,OTHER, +#2091,OTHER, +#2085,RESOURCE,snowflake_view +#2084,GRANTS,snowflake_grant_privileges_to_role +#2076,GRANTS,snowflake_grant_privileges_to_role +#2075,IDENTIFIERS,snowflake_pipe +#2073,OTHER, +#2072,GRANTS,snowflake_grant_privileges_to_role +#2070,IDENTIFIERS,snowflake_user +#2069,GRANTS,snowflake_grant_privileges_to_role +#2068,GRANTS,snowflake_grant_privileges_to_role +#2067,DATA_SOURCE,snowflake_current_account +#2060,GRANTS,snowflake_grant_datatabase_role +#2055,IMPORT,snowflake_view +#2054,RESOURCE,snowflake_masking_policy +#2053,RESOURCE,snowflake_row_access_policy +#2050,RESOURCE,snowflake_file_format +#2047,PROVIDER_CONFIG, +#2044,GRANTS,snowflake_grant_privileges_to_role +#2039,GRANTS, +#2036,RESOURCE,snowflake_task +#2035,IDENTIFIERS,snowflake_table_column_masking_policy_application +#2031,RESOURCE,snowflake_view +#2030,RESOURCE,snowflake_account +#2021,RESOURCE,snowflake_database +#2015,RESOURCE,snowflake_account +#2004,GRANTS,snowflake_grant_privileges_to_role +#2002,GRANTS,snowflake_grant_privileges_to_role +#1998,GRANTS,snowflake_grant_privileges_to_role +#1994,DOCUMENTATION,snowflake_file_format_grant +#1993,GRANTS,snowflake_grant_privileges_to_role +#1990,RESOURCE,snowflake_resource_monitor +#1987,OTHER, +#1985,OTHER, +#1984,SDK,snowflake_file_format +#1979,GRANTS,snowflake_grant_privileges_to_role +#1962,GRANTS, +#1957,RESOURCE,snowflake_resource_monitor +#1942,GRANTS,snowflake_grant_privileges_to_role +#1940,GRANTS,snowflake_grant_privileges_to_role +#1933,RESOURCE,snowflake_streamlit +#1932,GRANTS, +#1926,RESOURCE,snowflake_tag_association +#1925,GRANTS,snowflake_grants +#1922,GRANTS,snowflake_grant_privileges_to_share +#1911,RESOURCE,snowflake_stage +#1910,RESOURCE,snowflake_tag_association +#1909,RESOURCE,snowflake_tag_association +#1905,PROVIDER_CONFIG, +#1903,RESOURCE,snowflake_stage +#1901,RESOURCE,snowflake_external_function +#1893,GRANTS,snowflake_grant_privileges_to_role +#1891,RESOURCE,snowflake_account +#1888,RESOURCE,snowflake_event_table +#1884,RESOURCE,snowflake_warehouse +#1883,GRANTS, +#1881,PROVIDER_CONFIG, +#1877,GRANTS,snowflake_database_grant +#1875,GRANTS,snowflake_procedure_grant +#1862,RESOURCE,snowflake_tag +#1860,GRANTS,snowflake_account_grant +#1855,RESOURCE,snowflake_procedure +#1851,RESOURCE,snowflake_external_oauth_integration +#1848,RESOURCE,snowflake_object_parameter +#1845,GRANTS,snowflake_role_grants +#1844,RESOURCE,snowflake_warehouse +#1833,RESOURCE,snowflake_database +#1832,RESOURCE,snowflake_resource_monitor +#1823,OTHER, +#1821,RESOURCE,snowflake_resource_monitor +#1820,RESOURCE,snowflake_file_format +#1817,GRANTS,snowflake_grant_datatabase_role +#1815,GRANTS,snowflake_stage_grant +#1814,RESOURCE,snowflake_session_parameter +#1811,RESOURCE,snowflake_alert +#1806,RESOURCE,snowflake_tag +#1800,RESOURCE,snowflake_database +#1799,RESOURCE,snowflake_table_column_masking_policy_application +#1797,GRANTS,snowflake_failover_group_grant +#1796,GRANTS,snowflake_schema_grant +#1795,RESOURCE,snowflake_stage +#1794,GRANTS,snowflake_table_grant +#1784,DOCUMENTATION, +#1783,RESOURCE,snowflake_session_parameter +#1781,RESOURCE, +#1773,RESOURCE,snowflake_external_oauth_integration +#1770,IDENTIFIERS,snowflake_database +#1765,GRANTS,snowflake_external_table_grant +#1764,IDENTIFIERS,snowflake_table_column_masking_policy_application +#1761,RESOURCE,snowflake_task +#1760,RESOURCE,snowflake_file_format +#1759,OTHER, +#1757,RESOURCE,snowflake_database +#1754,RESOURCE,snowflake_resource_monitor +#1753,RESOURCE,snowflake_alert +#1745,DOCUMENTATION,snowflake_alert +#1741,RESOURCE,snowflake_external_oauth_integration +#1736,GRANTS,snowflake_function_grant +#1716,RESOURCE,snowflake_resource_monitor +#1714,RESOURCE,snowflake_resource_monitor +#1707,RESOURCE,snowflake_pipe +#1705,RESOURCE,snowflake_stage +#1700,PROVIDER_CONFIG, +#1695,RESOURCE,snowflake_procedure +#1693,RESOURCE,snowflake_email_notification_integration +#1692,OTHER, +#1691,GRANTS,snowflake_stage_grant +#1679,RESOURCE,snowflake_account_parameter +#1677,GRANTS,snowflake_schema_grant +#1671,RESOURCE,snowflake_account +#1657,RESOURCE,snowflake_tag +#1656,RESOURCE,snowflake_masking_policy +#1640,RESOURCE,snowflake_procedure +#1637,RESOURCE,snowflake_oauth_integration +#1632,GRANTS,snowflake_grant_privileges_to_share +#1630,DATA_SOURCE,snowflake_system_get_privatelink_config +#1624,RESOURCE,snowflake_resource_monitor +#1614,RESOURCE,snowflake_file_format +#1613,RESOURCE,snowflake_file_format +#1610,GRANTS,snowflake_schema_grant +#1609,RESOURCE,snowflake_file_format +#1607,RESOURCE,snowflake_account +#1602,RESOURCE,snowflake_replication_group +#1600,RESOURCE,snowflake_row_access_policy +#1594,GRANTS,snowflake_file_format_grant +#1593,GRANTS,snowflake_stage_grant +#1573,GRANTS,snowflake_schema_grant +#1572,RESOURCE,snowflake_user +#1565,RESOURCE,snowflake_session_parameter +#1564,RESOURCE,snowflake_external_table +#1563,GRANTS, +#1562,GRANTS, +#1561,RESOURCE,snowflake_object_parameter +#1553,GRANTS,snowflake_role_grants +#1546,RESOURCE,snowflake_session_parameter +#1544,DATA_SOURCE,snowflake_stages +#1542,RESOURCE,snowflake_account_parameter +#1537,RESOURCE,snowflake_external_table +#1535,RESOURCE,snowflake_user +#1534,GRANTS,snowflake_table_grant +#1527,RESOURCE,snowflake_database +#1526,RESOURCE,snowflake_view +#1517,GRANTS,snowflake_procedure_grant +#1503,RESOURCE,snowflake_external_oauth_integration +#1501,RESOURCE,snowflake_account +#1500,RESOURCE,snowflake_resource_monitor +#1498,RESOURCE,snowflake_external_oauth_integration +#1497,DATA_SOURCE,snowflake_shares +#1496,RESOURCE,snowflake_tag_association +#1491,RESOURCE,snowflake_stage +#1481,GRANTS,snowflake_table_grant +#1480,RESOURCE,snowflake_notification_integration +#1479,DATA_SOURCE,snowflake_functions +#1478,RESOURCE,snowflake_pipe +#1462,GRANTS,snowflake_account_grant +#1461,RESOURCE,snowflake_file_format +#1458,PROVIDER_CONFIG, +#1457,RESOURCE,snowflake_object_parameter +#1453,RESOURCE,snowflake_database +#1445,DATA_SOURCE,snowflake_integrations +#1444,RESOURCE,snowflake_masking_policy +#1443,RESOURCE,snowflake_tag +#1442,RESOURCE,snowflake_database_role +#1428,GRANTS,snowflake_warehouse_grant +#1425,GRANTS,snowflake_role_grants +#1422,RESOURCE,snowflake_masking_policy +#1421,RESOURCE,snowflake_oauth_integration +#1420,OTHER, +#1419,RESOURCE,snowflake_task +#1418,RESOURCE,snowflake_failover_group +#1416,RESOURCE,snowflake_external_table +#1408,GRANTS,snowflake_file_format_grant +#1394,RESOURCE,snowflake_tag +#1393,RESOURCE,snowflake_function +#1391,GRANTS,snowflake_role_grants +#1388,RESOURCE,snowflake_alert +#1387,IDENTIFIERS,snowflake_table +#1372,DATA_SOURCE,snowflake_tags +#1371,DATA_SOURCE,snowflake_databases +#1367,RESOURCE,snowflake_database +#1366,DATA_SOURCE,snowflake_current_account +#1339,GRANTS,snowflake_database_grant +#1299,GRANTS,snowflake_role_grants +#1298,GRANTS,snowflake_account_grant +#1285,GRANTS,snowflake_stage_grant +#1279,RESOURCE,snowflake_share +#1272,RESOURCE,snowflake_table +#1271,RESOURCE,snowflake_table +#1270,GRANTS, +#1269,DATA_SOURCE,snowflake_roles +#1267,, +#1253,RESOURCE,snowflake_view +#1250,RESOURCE,snowflake_task +#1249,, +#1248,RESOURCE,snowflake_table +#1243,RESOURCE,snowflake_schema +#1241,RESOURCE,snowflake_table +#1226,GRANTS,snowflake_schema_grant +#1224,RESOURCE,snowflake_scim_integration +#1221,, +#1219,GRANTS,snowflake_pipe_grant +#1218,RESOURCE,snowflake_materialized_view +#1212,GRANTS,snowflake_task_grant +#1208,RESOURCE,snowflake_function +#1204,, +#1200,GRANTS,snowflake_database_grant +#1195,RESOURCE,snowflake_procedure +#1194,RESOURCE,snowflake_task +#1189,RESOURCE,snowflake_procedure +#1182,DATA_SOURCE,snowflake_current_account +#1178,RESOURCE,snowflake_procedure +#1177,RESOURCE,snowflake_table_constraint +#1175,RESOURCE,snowflake_resource_monitor +#1169,OTHER, +#1164,GRANTS,snowflake_schema_grant +#1161,, +#1155,RESOURCE,snowflake_user +#1151,RESOURCE,snowflake_row_access_policy +#1150,RESOURCE,snowflake_stream +#1147,RESOURCE,snowflake_file_format +#1146,RESOURCE,snowflake_table +#1128,GRANTS, +#1104,RESOURCE,snowflake_warehouse +#1099,PROVIDER_CONFIG, +#1097,RESOURCE,snowflake_masking_policy +#1088,RESOURCE,snowflake_task +#1087,DOCUMENTATION,snowflake_stage +#1082,GRANTS,snowflake_role_grants +#1079,RESOURCE,snowflake_function +#1075,, +#1074,RESOURCE,snowflake_tag +#1068,, +#1067,, +#1066,, +#1062,RESOURCE,snowflake_organization_account +#1061,, +#1058,GRANTS,snowflake_account_grant +#1054,GRANTS, +#1051,RESOURCE,snowflake_notification_integration +#1050,RESOURCE,snowflake_procedure +#1049,RESOURCE,snowflake_view +#1045,RESOURCE,snowflake_database +#1044,OTHER, +#1042,, +#1040,RESOURCE,snowflake_external_table +#1036,RESOURCE,snowflake_session_parameter +#1034,GRANTS, +#1032,RESOURCE,snowflake_table +#1029,, +#1005,, +#1004,, +#1000,, +#998,, +#994,, +#993,, +#989,, +#984,, +#981,, +#980,, +#977,, +#976,, +#973,, +#970,, +#967,, +#966,, +#965,, +#963,, +#955,, +#953,, +#952,, +#951,, +#950,, +#946,, +#938,, +#908,, +#897,, +#896,, +#892,, +#891,, +#882,, +#867,, +#865,, +#859,, +#847,, +#845,, +#844,, +#836,, +#835,, +#825,, +#822,, +#821,, +#818,, +#810,, +#806,, +#802,, +#800,, +#794,, +#793,, +#791,, +#788,, +#787,, +#781,, +#778,, +#776,, +#774,, +#772,, +#770,, +#760,, +#757,, +#756,, +#754,, +#753,, +#749,, +#744,, +#741,, +#738,, +#737,, +#735,, +#723,, +#722,, +#719,, +#718,, +#712,, +#708,, +#690,, +#689,, +#688,, +#684,, +#683,, +#679,, +#678,, +#677,, +#676,, +#675,, +#674,, +#670,, +#665,, +#663,, +#660,, +#651,, +#648,, +#644,, +#642,, +#641,, +#640,, +#636,, +#630,RESOURCE,snowflake_share +#623,, +#621,, +#617,, +#616,, +#612,, +#611,, +#608,, +#606,, +#604,, +#601,, +#596,, +#594,, +#592,, +#587,, +#586,, +#583,, +#569,, +#568,, +#566,, +#564,, +#562,, +#560,, +#555,, +#551,, +#533,RESOURCE,snowflake_pipe +#531,, +#529,, +#522,, +#521,, +#514,, +#512,, +#509,, +#506,RESOURCE,snowflake_database +#502,, +#500,, +#498,, +#497,, +#494,, +#490,, +#474,, +#470,, +#466,, +#460,, +#455,, +#448,, +#447,, +#445,, +#430,, +#423,, +#421,, +#420,RESOURCE,snowflake_table +#419,, +#411,, +#410,, +#409,, +#408,, +#402,, +#384,, +#317,, +#308,, +#306,, +#303,, +#300,, +#295,, +#288,, +#285,, +#284,, +#282,, +#275,, +#273,, +#265,RESOURCE,snowflake_stage +#254,, +#244,, +#225,, +#222,, +#218,, +#211,, +#200,, +#189,, +#186,, +#152,, +#142,, +#137,, +#37,, diff --git a/pkg/scripts/issues/assign-labels/main.go b/pkg/scripts/issues/assign-labels/main.go new file mode 100644 index 0000000000..187a43e4c0 --- /dev/null +++ b/pkg/scripts/issues/assign-labels/main.go @@ -0,0 +1,195 @@ +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 ( + categoryLookupTable = make(map[string]string) + resourceLookupTable = make(map[string]string) + dataSourceLookupTable = 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] + labelValue := parts[1] + + switch labelType { + case "category": + categoryLookupTable[strings.ToUpper(labelValue)] = label + case "resource": + resourceLookupTable[fmt.Sprintf("snowflake_%s", labelValue)] = label + case "data_source": + dataSourceLookupTable[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[0][1:]) + if err != nil { + panic(err) + } + issues = append(issues, Issue{ + ID: id, + Category: record[1], + Object: record[2], + }) + } + 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 + } + + log.Printf("Assigning labels: %v to issue: %d", addLabelsRequestBody.Labels, issue.ID) + + 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 := categoryLookupTable[issue.Category]; ok { + switch issue.Category { + case "RESOURCE": + if resourceName, ok := resourceLookupTable[issue.Object]; ok { + return &AddLabelsRequestBody{ + Labels: []string{categoryLabel, resourceName}, + } + } + case "DATA_SOURCE": + if dataSourceName, ok := dataSourceLookupTable[issue.Object]; ok { + return &AddLabelsRequestBody{ + Labels: []string{categoryLabel, dataSourceName}, + } + } + } + + 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 +} diff --git a/pkg/scripts/issues/create-labels/main.go b/pkg/scripts/issues/create-labels/main.go new file mode 100644 index 0000000000..90da002bac --- /dev/null +++ b/pkg/scripts/issues/create-labels/main.go @@ -0,0 +1,161 @@ +package main + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "net/http" + "os" + "slices" + "strings" + "time" + + "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"` + Color string `json:"color"` +} + +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 + } + + 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": + requestBody, err = json.Marshal(&CreateLabelRequestBody{ + Name: label, + Description: fmt.Sprintf("Issue connected to the snowflake_%s resource", labelValue), + Color: "1D76DB", + }) + case "data_source": + requestBody, err = json.Marshal(&CreateLabelRequestBody{ + Name: label, + Description: fmt.Sprintf("Issue connected to the snowflake_%s data source", labelValue), + Color: "6321BE", + }) + 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 + } + + time.Sleep(1 * time.Second) + log.Println("Processing:", label) + + // based on https://docs.github.com/en/rest/issues/labels?apiVersion=2022-11-28#create-a-label + 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 { + responseBody, _ := io.ReadAll(resp.Body) + log.Println("incorrect status code, expected 201, and got:", resp.StatusCode, string(responseBody)) + 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 +} diff --git a/pkg/scripts/issues/labels.go b/pkg/scripts/issues/labels.go new file mode 100644 index 0000000000..cb5a130d3e --- /dev/null +++ b/pkg/scripts/issues/labels.go @@ -0,0 +1,106 @@ +package issues + +var RepositoryLabels = []string{ + "category:resource", + "category:data_source", + "category:import", + "category:sdk", + "category:identifiers", + "category:provider_config", + "category:grants", + "category:other", + "resource:account", + "resource:account_parameter", + "resource:account_password_policy", + "resource:alert", + "resource:api_integration", + "resource:database", + "resource:database_role", + "resource:dynamic_table", + "resource:email_notification_integration", + "resource:external_function", + "resource:external_oauth_integration", + "resource:external_table", + "resource:failover_group", + "resource:file_format", + "resource:function", + "resource:grant_account_role", + "resource:grant_database_role", + "resource:grant_ownership", + "resource:grant_privileges_to_account_role", + "resource:grant_privileges_to_database_role", + "resource:grant_privileges_to_share", + "resource:managed_account", + "resource:masking_policy", + "resource:materialized_view", + "resource:network_policy", + "resource:network_policy_attachment", + "resource:notification_integration", + "resource:oauth_integration", + "resource:object_parameter", + "resource:password_policy", + "resource:pipe", + "resource:procedure", + "resource:resource_monitor", + "resource:role", + "resource:row_access_policy", + "resource:saml_integration", + "resource:schema", + "resource:scim_integration", + "resource:sequence", + "resource:session_parameter", + "resource:share", + "resource:stage", + "resource:storage_integration", + "resource:stream", + "resource:table", + "resource:table_column_masking_policy_application", + "resource:table_constraint", + "resource:tag", + "resource:tag_association", + "resource:tag_masking_policy_association", + "resource:task", + "resource:unsafe_execute", + "resource:user", + "resource:user_password_policy_attachment", + "resource:user_public_keys", + "resource:view", + "resource:warehouse", + "data_source:accounts", + "data_source:alerts", + "data_source:current_account", + "data_source:current_role", + "data_source:database", + "data_source:database_roles", + "data_source:databases", + "data_source:dynamic_tables", + "data_source:external_functions", + "data_source:external_tables", + "data_source:failover_groups", + "data_source:file_formats", + "data_source:functions", + "data_source:grants", + "data_source:masking_policies", + "data_source:materialized_views", + "data_source:parameters", + "data_source:pipes", + "data_source:procedures", + "data_source:resource_monitors", + "data_source:roles", + "data_source:row_access_policies", + "data_source:schemas", + "data_source:sequences", + "data_source:shares", + "data_source:stages", + "data_source:storage_integrations", + "data_source:streams", + "data_source:system_generate_scim_access_token", + "data_source:system_get_aws_sns_iam_policy", + "data_source:system_get_privatelink_config", + "data_source:system_get_snowflake_platform_info", + "data_source:tables", + "data_source:tasks", + "data_source:users", + "data_source:views", + "data_source:warehouses", +}