mirror of
https://github.com/cloudflare/cloudflare-docs.git
synced 2026-01-11 20:06:58 +00:00
feat(tools): support syncing issues internally (#8621)
Introduces some automation to take the GitHub issue and sync it internally allowing us to map internal/external and track in both systems.
This commit is contained in:
parent
ac7bda2c71
commit
875010629a
6 changed files with 268 additions and 0 deletions
36
.github/workflows/issue-sync.yml
vendored
Normal file
36
.github/workflows/issue-sync.yml
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
name: Sync issues internally
|
||||
on:
|
||||
issues:
|
||||
types: [created]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
issue_number:
|
||||
description: The issue to target
|
||||
required: true
|
||||
jobs:
|
||||
internal-issue-sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: 'tools/go.mod'
|
||||
- run: make tools
|
||||
- name: Set the issue number to sync
|
||||
run: |
|
||||
if [ -n "${{ github.event.inputs.issue_number }}" ]; then
|
||||
echo "issue_number=${{ github.event.inputs.issue_number }}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "issue_number=${{ github.event.issue.number }}" >> $GITHUB_ENV
|
||||
fi
|
||||
- run: go run cmd/sync-github-issue-to-jira/main.go ${{ env.issue_number }}
|
||||
working-directory: ./tools
|
||||
env:
|
||||
GITHUB_OWNER: cloudflare
|
||||
GITHUB_REPO: cloudflare-docs
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
JIRA_HOSTNAME: ${{ secrets.JIRA_HOSTNAME }}
|
||||
JIRA_AUTH_TOKEN: ${{ secrets.JIRA_AUTH_TOKEN }}
|
||||
CF_ACCESS_CLIENT_ID: ${{ secrets.CF_ACCESS_CLIENT_ID }}
|
||||
CF_ACCESS_CLIENT_SECRET: ${{ secrets.CF_ACCESS_CLIENT_SECRET }}
|
||||
4
Makefile
4
Makefile
|
|
@ -9,3 +9,7 @@ hugo:
|
|||
build: download hugo
|
||||
npm run build:local
|
||||
./minify -r public -o .
|
||||
|
||||
tools:
|
||||
@echo "==> Installing development tooling..."
|
||||
go generate -tags tools tools/tools.go
|
||||
|
|
|
|||
172
tools/cmd/sync-github-issue-to-jira/main.go
Normal file
172
tools/cmd/sync-github-issue-to-jira/main.go
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-github/github"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type IssueName struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type IssueValue struct {
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type IssueKey struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type IssueFields struct {
|
||||
Project IssueKey `json:"project"`
|
||||
Summary string `json:"summary"`
|
||||
Description string `json:"description"`
|
||||
MyTeam IssueValue `json:"customfield_14803"`
|
||||
IssueType IssueName `json:"issuetype"`
|
||||
Components []Component `json:"components"`
|
||||
}
|
||||
|
||||
type Component struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type InternalIssue struct {
|
||||
Fields IssueFields `json:"fields"`
|
||||
}
|
||||
|
||||
type IssueCreationResponse struct {
|
||||
ID string `json:"id"`
|
||||
Key string `json:"key"`
|
||||
Self string `json:"self"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
if len(os.Args) < 2 {
|
||||
log.Fatalf("Usage: sync-github-issue-to-jira <GitHub issue number>\n")
|
||||
}
|
||||
iss := os.Args[1]
|
||||
issueNumber, err := strconv.Atoi(iss)
|
||||
if err != nil {
|
||||
log.Fatalf("error parsing issue %q as a number: %s", iss, err)
|
||||
}
|
||||
|
||||
githubRepositoryOwner := os.Getenv("GITHUB_OWNER")
|
||||
githubRepositoryName := os.Getenv("GITHUB_REPO")
|
||||
githubAccessToken := os.Getenv("GITHUB_TOKEN")
|
||||
jiraHostname := os.Getenv("JIRA_HOSTNAME")
|
||||
jiraAuthToken := os.Getenv("JIRA_AUTH_TOKEN")
|
||||
accessClientID := os.Getenv("CF_ACCESS_CLIENT_ID")
|
||||
accessClientSecret := os.Getenv("CF_ACCESS_CLIENT_SECRET")
|
||||
|
||||
if githubRepositoryOwner == "" {
|
||||
log.Fatal("GITHUB_OWNER not set")
|
||||
}
|
||||
|
||||
if githubRepositoryName == "" {
|
||||
log.Fatal("GITHUB_REPO not set")
|
||||
}
|
||||
|
||||
if githubAccessToken == "" {
|
||||
log.Fatal("GITHUB_TOKEN not set")
|
||||
}
|
||||
|
||||
if jiraHostname == "" {
|
||||
log.Fatal("JIRA_HOSTNAME not set")
|
||||
}
|
||||
|
||||
if jiraAuthToken == "" {
|
||||
log.Fatal("JIRA_AUTH_TOKEN not set")
|
||||
}
|
||||
|
||||
if accessClientID == "" {
|
||||
log.Fatal("CF_ACCESS_CLIENT_ID not set")
|
||||
}
|
||||
|
||||
if accessClientSecret == "" {
|
||||
log.Fatal("CF_ACCESS_CLIENT_SECRET not set")
|
||||
}
|
||||
|
||||
ts := oauth2.StaticTokenSource(
|
||||
&oauth2.Token{AccessToken: githubAccessToken},
|
||||
)
|
||||
tc := oauth2.NewClient(ctx, ts)
|
||||
|
||||
client := github.NewClient(tc)
|
||||
|
||||
issue, _, err := client.Issues.Get(ctx, githubRepositoryOwner, githubRepositoryName, issueNumber)
|
||||
if err != nil {
|
||||
log.Fatalf("error retrieving issue %s/%s#%d: %s", githubRepositoryOwner, githubRepositoryName, issueNumber, err)
|
||||
}
|
||||
|
||||
newIssue := InternalIssue{Fields: IssueFields{
|
||||
Project: IssueKey{Key: "PCX"},
|
||||
Summary: *issue.Title,
|
||||
Description: jirafyBodyMarkdown(issue),
|
||||
MyTeam: IssueValue{Value: "Product Management"},
|
||||
IssueType: IssueName{Name: "Task"},
|
||||
Components: []Component{{Name: "Other (Unknown)"}},
|
||||
}}
|
||||
|
||||
res, err := json.Marshal(newIssue)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("https://%s/rest/api/latest/issue/", jiraHostname)
|
||||
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(res))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to build HTTP request: %s", err)
|
||||
}
|
||||
|
||||
req.Header.Set("authorization", "Basic "+jiraAuthToken)
|
||||
req.Header.Set("cf-access-client-id", accessClientID)
|
||||
req.Header.Set("cf-access-client-secret", accessClientSecret)
|
||||
req.Header.Set("content-type", "application/json")
|
||||
|
||||
httpClient := &http.Client{}
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to read response body: %s", err)
|
||||
}
|
||||
|
||||
var createdIssue IssueCreationResponse
|
||||
json.Unmarshal([]byte(body), &createdIssue)
|
||||
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
fmt.Println(fmt.Sprintf("failed to create new JIRA issue"))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println(fmt.Sprintf("successfully created internal JIRA issue: %s", createdIssue.Key))
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// jirafyBodyMarkdown takes GitHub markdown and makes it palatable for JIRA
|
||||
// with reasonable formatting.
|
||||
func jirafyBodyMarkdown(issue *github.Issue) string {
|
||||
output := "GitHub issue: " + *issue.HTMLURL + "\n\n---\n\n"
|
||||
|
||||
output += *issue.Body
|
||||
output = strings.ReplaceAll(output, "###", "h3.")
|
||||
|
||||
return output
|
||||
}
|
||||
16
tools/go.mod
Normal file
16
tools/go.mod
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
module github.com/cloudflare/cloudflare-docs/tools
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/google/go-github v17.0.0+incompatible
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
)
|
||||
28
tools/go.sum
Normal file
28
tools/go.sum
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
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.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA=
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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/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=
|
||||
12
tools/tools.go
Normal file
12
tools/tools.go
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
//go:build tools
|
||||
// +build tools
|
||||
|
||||
package tools
|
||||
|
||||
//go:generate go install github.com/google/go-github/github
|
||||
//go:generate go install golang.org/x/oauth2
|
||||
|
||||
import (
|
||||
_ "github.com/google/go-github/github"
|
||||
_ "golang.org/x/oauth2"
|
||||
)
|
||||
Loading…
Add table
Reference in a new issue