From 8f02ae58e6c40e5ef22f3dee838302cb8ebff648 Mon Sep 17 00:00:00 2001 From: Rosyrain <116946548+Rosyrain@users.noreply.github.com> Date: Mon, 9 Sep 2024 11:23:59 +0800 Subject: [PATCH] reconfiguration reopen-without-PR action (#122) --- .github/workflows/robot.yaml | 15 +- actions/reopen-without-PR/action.yml | 16 +- actions/reopen-without-PR/main.go | 262 +++++++++++------- .../reopen-without-PR/pkg/config/config.go | 5 +- actions/reopen-without-PR/pkg/config/get.go | 17 +- 5 files changed, 202 insertions(+), 113 deletions(-) diff --git a/.github/workflows/robot.yaml b/.github/workflows/robot.yaml index ee3221c..e7c9d43 100644 --- a/.github/workflows/robot.yaml +++ b/.github/workflows/robot.yaml @@ -310,14 +310,15 @@ jobs: steps: - name: Reopen Issue Without PR id: step1 - env: - BASE_URL: https://api.github.com with: - OWNER: ${{ github.repository_owner }} # 获取执行该作业的仓库作者 形如: matrixorigin - REPOSITORY: ${{ github.repository }} # 获取执行该作业的仓库名 形如: matrixorigin/CI - ISSUE_NUMBER: ${{ github.event.issue.number }} # 获取当前操作的issue number - GITHUB_TOKEN: ${{ secrets.TOKEN_ACTION }} # 获取github action token操作权限 - ASSIGNEES: sukki37 #issue指定分配对象 + whitelist: 'sukki37,fengttt,aressu1985' # 白名单成员 可以无条件删除带有特殊标签的issue + issue_owner: ${{ github.event.issue.user.login }} # issue的打开者 当满足存在关联pr时允许关闭 + close_user: ${{ github.event.sender.login }} # 当前关闭issue的操作者 + github_token: ${{ secrets.TOKEN_ACTION }} # 获取github action token操作权限 + base_url: ${{ github.api_url }} + repository: ${{ github.repository }} # 获取执行该作业的仓库名 形如: matrixorigin/CI + issue_number: ${{ github.event.issue.number }} # 获取当前操作的issue number + assignees: 'sukki37' #issue指定分配对象 LABELS: 'no-pr-linked' #相关自定义label配置 形如 label1,label2 LABELS_NEED: 'tech-request,feature,Feature,kind/feature,attention/feature-incomplete,bug/ut,Bug fix,kind/bug,kind/subtask,kind/tech-request' # 指定需要排查的标签 label1,label2,... uses: matrixorigin/CI/actions/reopen-without-PR@main diff --git a/actions/reopen-without-PR/action.yml b/actions/reopen-without-PR/action.yml index f2b647b..c87f5bf 100644 --- a/actions/reopen-without-PR/action.yml +++ b/actions/reopen-without-PR/action.yml @@ -1,10 +1,7 @@ -name: Reopen issue Without PR +name: reopen-without-PR author: rosyrain description: When an issue is closed, if there is no associated pull_request, the labels are automatically added and assigned to the specified member. inputs: - owner: - description: The owner of the repository - required: true repository: description: The repository`s name required: true @@ -23,7 +20,18 @@ inputs: default: 'no-pr-linked' labels_need: description: Special labels that need to be handled + required: false default: 'tech-request,feature,Feature,kind/feature,attention/feature-incomplete,bug/ut,Bug fix,kind/bug,kind/subtask,kind/tech-request' + whitelist: + description: whitelist who can close issue + required: false + default: "sukki77,fengttt,aressu1985" + issue_owner: + require: true + description: issue owner + close_user: + description: close issue user + require: true outputs: send: description: Whether to send a notification diff --git a/actions/reopen-without-PR/main.go b/actions/reopen-without-PR/main.go index 2e6268b..32d9398 100644 --- a/actions/reopen-without-PR/main.go +++ b/actions/reopen-without-PR/main.go @@ -13,72 +13,109 @@ import ( "strings" ) -func main() { +// 实现思路 +// 1. 加载配置 +// 2. 是否为需要处理的issue +// 3. 对关闭该issue的用户进行身份校验 +// 3.1 若用户是白名单用户,则直接结束,不做处理 +// 3.2 若用户是issue的owner,判断是否存在对关联pr;存在则不做处理,反之 +// 3.3 若用户非issue的owner,白名单用户,则直接进行后续处理 + +var ( // 设置GitHub API的基本URL和认证信息 //Examples of parameters are as follows - //baseURL := "https://api.github.com" - //owner := "Rosyrain" - //repo := "rosyrain/github_action_test" - //issueNumber := 21 - //token := "xxxxxxx" - //assignees := "Rosyrain" - //labelData := `no-pr-linked-test2,test3` - //labelsNeed := `tech-request,feature,Feature,kind/feature,attention/feature-incomplete,bug/ut,Bug fix,kind/bug,kind/subtask,kind/tech-request` - - baseURL := config.GetBaseURL() - owner := config.GetOwner() - repo := config.GetRepository() - issueNumber := config.GetIssueNumber() // 你要检查的issue编号 - token := config.GetGithubToken() // 从环境变量中获取GitHub访问令牌 - assignees := config.GetAssignees() - labelData := config.GetLabels() - labelsNeed := config.GetLabelsNeed() - - fmt.Println("owner:", owner) - hasRelatedPR := false // 是否存在关联pr - - // 1.获取issue时间线判断是否存在pr - // 构建请求URL - issueURL := fmt.Sprintf("%s/repos/%s/issues/%d/timeline", baseURL, repo, issueNumber) + //baseURL = "https://api.github.com" + //owner = "Rosyrain" + //repo = "rosyrain/github_action_test" + //issueNumber = 20 + //token = "xxx" + //assignees = "Rosyrain" + //labelData = `no-pr-linked-test2,test3` + //labelsNeed = `tech-request,feature,Feature,kind/feature,attention/feature-incomplete,bug/ut,Bug fix,kind/bug,kind/subtask,kind/tech-request` + + baseURL = config.GetBaseURL() + issueOwner = config.GetIssueOwner() + repo = config.GetRepository() + issueNumber = config.GetIssueNumber() // 你要检查的issue编号 + token = config.GetGithubToken() // 从环境变量中获取GitHub访问令牌 + assignees = config.GetAssignees() + addLabelData = config.GetAddLabels() + labelsNeed = config.GetLabelsNeed() + whitelist = config.GetWhiteList() + closeUser = config.GetCloseUser() +) - // 创建请求头,包含认证信息 - issueResp, err := ihttp.Request("GET", issueURL, token, nil, "") +// task 任务处理函数-- 添加标签-->转交sukki37-->reopen issue +func task() { + // 如果没有关联的pull request,给issue添加标签并转交给sukki37 + // 添加标签 + labelURL := fmt.Sprintf("%s/repos/%s/issues/%d/labels", baseURL, repo, issueNumber) + labelSlice := strings.Split(addLabelData, ",") + jsonData, err := json.Marshal(labelSlice) if err != nil { - panic(fmt.Sprintf("issueUrl http.Request failed,err:%v", err)) + panic("json.Marshal labelSlice failed") } - if issueResp.StatusCode != http.StatusOK { - panic(fmt.Sprintf("connect issueUrl failed,resp.statusCode:%d", issueResp.StatusCode)) + labelResp, err := ihttp.Request("POST", labelURL, token, bytes.NewReader(jsonData), "application/json") + if err != nil { + panic(fmt.Sprintf("Error adding label,err:%v", err)) } - defer issueResp.Body.Close() - fmt.Printf("get issue %d info successfully.\n", issueNumber) + if labelResp.StatusCode != http.StatusOK { + panic(fmt.Sprintf("labelURL http.Request failed,labelStatusCode:%d", labelResp.StatusCode)) + } + defer labelResp.Body.Close() + fmt.Printf("Add labels %s successfully.\n", addLabelData) - // 解析响应内容(这里省略了具体的解析过程,你可以根据需要自行处理) - // 检查issue是否有关联的pull request - body, err := io.ReadAll(issueResp.Body) + // 转交给sukki37 + assigneeURL := fmt.Sprintf("%s/repos/%s/issues/%d/assignees", baseURL, repo, issueNumber) + assigneeData := map[string]string{"assignees": assignees} + jsonData, err = json.Marshal(assigneeData) if err != nil { - panic(fmt.Sprintf("Error reading issue response body:%v", err)) + panic("json.Marshal assigneeData failed") } - var issueData []interface{} - err = json.Unmarshal(body, &issueData) + assigneeResp, err := ihttp.Request("POST", assigneeURL, token, bytes.NewReader(jsonData), "application/json") if err != nil { - panic(fmt.Sprintf("Error unmarshalling JSON:%v", err)) + panic(fmt.Sprintf("Error creating assignee request:%v", err)) + } + if assigneeResp.StatusCode != 201 { + panic(fmt.Sprintf("Failed to assign a problem to a specified person,assigneeStatusCode:%d", assigneeResp.StatusCode)) } + defer assigneeResp.Body.Close() + fmt.Printf("Issue labeled and assigned to %s successfully.\n", assignees) - for _, data := range issueData { - dataMap := data.(map[string]interface{}) - typeEvent := dataMap["event"] - if typeEvent == "cross-referenced" { - sourceMap := dataMap["source"].(map[string]interface{}) - issueMap := sourceMap["issue"].(map[string]interface{}) - _, ok := issueMap["pull_request"] - if ok { - hasRelatedPR = true - break - } - } + //重新打开issue + reopenURL := fmt.Sprintf("%s/repos/%s/issues/%d", baseURL, repo, issueNumber) + reopenData := map[string]string{"state": "open"} + jsonData, err = json.Marshal(reopenData) + if err != nil { + panic("json.Marshal reopenData failed") } + reopenResp, err := ihttp.Request("PATCH", reopenURL, token, bytes.NewReader(jsonData), "application/json") + if err != nil { + panic(fmt.Sprintf("Error creating reopen request:%v", err)) + } + if reopenResp.StatusCode != http.StatusOK { + panic(fmt.Sprintf("Failed to reopen topic,reopenStatusCode:%d", reopenResp.StatusCode)) + } + defer reopenResp.Body.Close() + fmt.Printf("Issue %d reopen successfully.\n", issueNumber) + if err := github.SetOutput("send", "yes"); err != nil { + fmt.Println("set outputs failed") + } +} - //2.判断是否为指定labels +func main() { + // 打印相关信息 + fmt.Printf("whitelist:%s\n", whitelist) + fmt.Printf("issueOwner:%s\n", issueOwner) + fmt.Printf("closeUser:%s\n", closeUser) + fmt.Printf("baseURL:%s\n", baseURL) + fmt.Printf("repo:%s\n", repo) + fmt.Printf("issueNumber:%d\n", issueNumber) + fmt.Printf("assignees:%s\n", assignees) + fmt.Printf("addLabels:%s\n", addLabelData) + fmt.Printf("labelsNeed:%s\n", labelsNeed) + + //1.判断是否为指定labels labelNeedURL := fmt.Sprintf("%s/repos/%s/issues/%d/labels", baseURL, repo, issueNumber) labelNeedResp, err := ihttp.Request("GET", labelNeedURL, token, nil, "") if err != nil { @@ -118,64 +155,97 @@ func main() { } } - //3.进行加标签转交指定人员 - if !hasRelatedPR && hasSame { - // 如果没有关联的pull request,给issue添加标签并转交给sukki37 - // 添加标签 - labelURL := fmt.Sprintf("%s/repos/%s/issues/%d/labels", baseURL, repo, issueNumber) - labelSlice := strings.Split(labelData, ",") - jsonData, err := json.Marshal(labelSlice) - if err != nil { - panic("json.Marshal labelSlice failed") - } - labelResp, err := ihttp.Request("POST", labelURL, token, bytes.NewReader(jsonData), "application/json") - if err != nil { - panic(fmt.Sprintf("Error adding label,err:%v", err)) - } - if labelResp.StatusCode != http.StatusOK { - panic(fmt.Sprintf("labelURL http.Request failed,labelStatusCode:%d", labelResp.StatusCode)) + if !hasSame { + fmt.Printf("this issue don`t have special labels") + if err := github.SetOutput("send", "no"); err != nil { + fmt.Println("set outputs failed") } - defer labelResp.Body.Close() - fmt.Printf("Add labels %s successfully.\n", labelData) + return + } - // 转交给sukki37 - assigneeURL := fmt.Sprintf("%s/repos/%s/issues/%d/assignees", baseURL, repo, issueNumber) - assigneeData := map[string]string{"assignees": assignees} - jsonData, err = json.Marshal(assigneeData) - if err != nil { - panic("json.Marshal assigneeData failed") + // 2.1判断是否为白名单人员 + whiteSlice := strings.Split(whitelist, ",") + + for _, user := range whiteSlice { + if closeUser == user { + fmt.Printf("close issue user %s is in whitelist\n", user) + if err := github.SetOutput("send", "no"); err != nil { + fmt.Println("set outputs failed") + } + return } - assigneeResp, err := ihttp.Request("POST", assigneeURL, token, bytes.NewReader(jsonData), "application/json") + } + + // 进行2.2/2.3判断 -- 已经确定非白名单用户 + // 2.3非白名单非issue的owner直接进行处理 + if closeUser != issueOwner { + fmt.Printf("closeUser %s is not issueOwner %s,will exec task...", closeUser, issueOwner) + task() + return + } + + // 2.2 issue_owner用户处理 + hasRelatedPR := false // 是否存在关联pr + + // 1.获取issue时间线判断是否存在pr + // 构建请求URL + for page := 1; page <= 100; page++ { + fmt.Printf("get issue info,page=%d,per_page=100\n", page) + issueURL := fmt.Sprintf("%s/repos/%s/issues/%d/timeline?page=%d&per_page=100", baseURL, repo, issueNumber, page) + + // 创建请求头,包含认证信息 + issueResp, err := ihttp.Request("GET", issueURL, token, nil, "") if err != nil { - panic(fmt.Sprintf("Error creating assignee request:%v", err)) + panic(fmt.Sprintf("issueUrl http.Request failed,err:%v", err)) } - if assigneeResp.StatusCode != 201 { - panic(fmt.Sprintf("Failed to assign a problem to a specified person,assigneeStatusCode:%d", assigneeResp.StatusCode)) + if issueResp.StatusCode != http.StatusOK { + panic(fmt.Sprintf("connect issueUrl failed,resp.statusCode:%d", issueResp.StatusCode)) } - defer assigneeResp.Body.Close() - fmt.Printf("Issue labeled and assigned to %s successfully.\n", assignees) + defer issueResp.Body.Close() + fmt.Printf("get issue %d info successfully.\n", issueNumber) - //重新打开issue - reopenURL := fmt.Sprintf("%s/repos/%s/issues/%d", baseURL, repo, issueNumber) - reopenData := map[string]string{"state": "open"} - jsonData, err = json.Marshal(reopenData) + // 解析响应内容(这里省略了具体的解析过程,你可以根据需要自行处理) + // 检查issue是否有关联的pull request + body, err := io.ReadAll(issueResp.Body) if err != nil { - panic("json.Marshal reopenData failed") + panic(fmt.Sprintf("Error reading issue response body:%v", err)) } - reopenResp, err := ihttp.Request("PATCH", reopenURL, token, bytes.NewReader(jsonData), "application/json") + var issueData []interface{} + err = json.Unmarshal(body, &issueData) if err != nil { - panic(fmt.Sprintf("Error creating reopen request:%v", err)) + panic(fmt.Sprintf("Error unmarshalling JSON:%v", err)) } - if reopenResp.StatusCode != http.StatusOK { - panic(fmt.Sprintf("Failed to reopen topic,reopenStatusCode:%d", reopenResp.StatusCode)) + + // 如果获取到到数据为空,则说明已经全部获取了,退出循环 + if len(issueData) == 0 { + break } - defer reopenResp.Body.Close() - fmt.Printf("Issue %d reopen successfully.\n", issueNumber) - if err := github.SetOutput("send", "yes"); err != nil { - fmt.Println("set outputs failed") + + for _, data := range issueData { + dataMap := data.(map[string]interface{}) + typeEvent := dataMap["event"] + if typeEvent == "cross-referenced" { + sourceMap := dataMap["source"].(map[string]interface{}) + issueMap := sourceMap["issue"].(map[string]interface{}) + _, ok := issueMap["pull_request"] + if ok { + hasRelatedPR = true + break + } + } + } + if hasRelatedPR { + break } + } + + //3.进行加标签转交指定人员 + if !hasRelatedPR { + fmt.Println("dont found related PR...") + task() + return } else { - fmt.Println("Issue has related pull requests or don`t find need issue, no action taken.") + fmt.Println("Issue has related pull requests or don`t find need issue, no action token.") if err := github.SetOutput("send", "no"); err != nil { fmt.Println("set outputs failed") } diff --git a/actions/reopen-without-PR/pkg/config/config.go b/actions/reopen-without-PR/pkg/config/config.go index 16f9e6c..f83877a 100644 --- a/actions/reopen-without-PR/pkg/config/config.go +++ b/actions/reopen-without-PR/pkg/config/config.go @@ -1,11 +1,14 @@ package config const ( - EnvOwner = "owner" + EnvBaseURL = "base_url" EnvRepository = "repository" EnvIssueNumber = "issue_number" EnvGithubToken = "github_token" EnvAssignees = "assignees" EnvLabels = "labels" EnvLabelsNeed = "labels_need" + EnvWhitelist = "whitelist" + EnvIssueOwner = "issue_owner" + EnvCloseUser = "close_user" ) diff --git a/actions/reopen-without-PR/pkg/config/get.go b/actions/reopen-without-PR/pkg/config/get.go index 8014b73..9bc65fb 100644 --- a/actions/reopen-without-PR/pkg/config/get.go +++ b/actions/reopen-without-PR/pkg/config/get.go @@ -2,17 +2,16 @@ package config import ( "fmt" - "os" "reopen-without-PR/pkg/github" "strconv" ) func GetBaseURL() string { - return os.Getenv("BASE_URL") + return github.MustGetInput(EnvBaseURL) } -func GetOwner() string { - return github.MustGetInput(EnvOwner) +func GetIssueOwner() string { + return github.MustGetInput(EnvIssueOwner) } func GetRepository() string { @@ -40,10 +39,18 @@ func GetAssignees() string { return github.MustGetInput(EnvAssignees) } -func GetLabels() string { +func GetAddLabels() string { return github.MustGetInput(EnvLabels) } func GetLabelsNeed() string { return github.MustGetInput(EnvLabelsNeed) } + +func GetWhiteList() string { + return github.MustGetInput(EnvWhitelist) +} + +func GetCloseUser() string { + return github.MustGetInput(EnvCloseUser) +}