Go:检查 GitHub 上的公共存储库列表。Go 切片比较。第一次 Golang 体验。
将由 Jenkins 的 cron 作业启动的工具,用于检查 GitHub 上某个组织的公共存储库列表
引言
任务是编写一个工具,该工具将由 Jenkins 的 cron 作业启动,并检查 GitHub 上某个组织的公共存储库列表。
然后,它需要将收到的存储库列表与预定义的允许存储库列表进行比较,如果它们不相等,则向 Slack 频道发送警报。
这样做的目的是在开发人员意外地将私有存储库创建为公共存储库,或者将公共存储库更改为私有存储库时,能够进行此类检查,并收到有关此问题的通知。
可以使用 bash
和 curl
,或 Python 的 urllib
并直接使用 Github API 来编写,但我希望获得一些 Go 体验,所以将使用 Go。
实际上,这是我第一个自己编写的 Golang 代码,我并没有太多使用它,甚至不知道它的语法,但我将尝试使用一些现有的 C/Python 知识,添加一些逻辑,当然,还有 Google 的帮助,写出一些可行的东西。
要使用 Github API,将使用 go-github
包。
开始吧。
添加导入,该函数是使用 vim-go
插件自动创建的
package main import ( "fmt" "github.com/google/go-github/github" ) func main() { fmt.Println("vim-go") }
添加 $GOPATH
$ sudo mkdir /usr/local/go
$ sudo chown setevoy:setevoy /usr/local/go/
$ export GOPATH=/usr/local/go && export GOBIN=/usr/local/go/bin
安装包
$ go get
_/home/setevoy/Scripts/Go/GTHB
./gitrepos.go:5:2: imported and not used: "github.com/google/go-github/github"
从 GitHub 获取存储库列表
复制并粘贴包 README 中的第一个示例,组织名称将从 $GITHUB_ORG_NAME
获取,让我们尝试获取一些内容。
设置变量
$ export GITHUB_ORG_NAME="rtfmorg"
然后是代码
package main import ( "context" "fmt" "github.com/google/go-github/github" ) func main() { client := github.NewClient(nil) opt := &github.RepositoryListByOrgOptions{Type: "public"} repos, _, _ := client.Repositories.ListByOrg(context.Background(), os.Getenv("GITHUB_ORG_NAME"), opt) fmt.Printf(repos) }
在原始示例中,组织名称是硬编码的,但在这里我们将从一个变量中获取它,该变量将在 Jenkins 作业中使用 os.Getenv
设置。
添加 context
和 os
导入。
运行代码
$ go run go-github-public-repos-checker.go
command-line-arguments
./go-github-public-repos-checker.go:17:15:
cannot use repos (type []*github.Repository) as type string in argument to fmt.Printf
呃……
好的。
我以为 client.Repositories.ListByOrg
会返回一个列表——仅仅因为它的名字里有“List”。
检查 repos
对象中的数据类型。使用 reflect
package main import ( "os" "context" "fmt" "reflect" "github.com/google/go-github/github" ) func main() { client := github.NewClient(nil) opt := &github.RepositoryListByOrgOptions{Type: "public"} repos, _, _ := client.Repositories.ListByOrg(context.Background(), os.Getenv("GITHUB_ORG_NAME"), opt) fmt.Println(reflect.TypeOf(repos).String()) }
Run
$ go run go-github-public-repos-checker.go
[]*github.Repository
好的……它确实是一个列表([]
),显然指向一个结构体——github.Repository
。
我们用 go doc
检查一下
go doc github.Repository
type Repository struct {
ID *int64 `json:"id,omitempty"`
NodeID *string `json:"node_id,omitempty"`
Owner *User `json:"owner,omitempty"`
Name *string `json:"name,omitempty"`
FullName *string `json:"full_name,omitempty"`
Description *string `json:"description,omitempty"`
...
是的——它是一个结构体,我们也能看到它所有的字段。
要禁用“导入但未使用:“reflect””消息——将其设置为 _reflect
package main import ( "os" "context" "fmt" _"reflect" "github.com/google/go-github/github" ) func main() { client := github.NewClient(nil) opt := &github.RepositoryListByOrgOptions{Type: "public"} repos, _, _ := client.Repositories.ListByOrg(context.Background(), os.Getenv("GITHUB_ORG_NAME"), opt) for _, repo := range repos { fmt.Printf("Repo: %s\n", *repo.Name) } }
这里从 repos
列表中,我们获取元素的 ID 及其值。
也可以添加 ID
... for id, repo := range repos { fmt.Printf("%d Repo: %s\n", id, *repo.Name) } ...
$ go run go-github-public-repos-checker.go
0 Repo: org-repo-1-pub
1 Repo: org-repo-2-pub
在 Go 中比较列表
现在需要添加另一个列表来存储来自 GitHub 的存储库列表。
允许的 public
存储库将从 Jenkins 以空格分隔的列表视图传递
$ export ALLOWED_REPOS="1 2"
第一次尝试
创建 string
类型的 allowedRepos
并将其保存为允许的 public
存储库列表
... allowedRepos := []string{os.Getenv("ALLOWED_REPOS")} for id, repo := range repos { fmt.Printf("%d Repo: %s\n", id, *repo.Name) } fmt.Printf("Allowed repos: %s\n", allowedRepos) ...
但这里有一个问题,我在稍后比较列表时遇到了,我们很快就会看到。
运行代码
$ go run go-github-public-repos-checker.go
0 Repo: org-repo-1-pub
1 Repo: org-repo-2-pub
Allowed repos: [1 2]
好的——它奏效了。
现在,我们需要在两个存储库列表——来自 GitHub 的 repos
和 allowedRepos
——之间进行检查。
第一个解决方案是这样的
... allowedRepos := []string{os.Getenv("ALLOWED_REPOS")} fmt.Printf("Allowed repos: %s\n", allowedRepos) for id, repo := range repos { for _, i := range allowedRepos { if i == *repo.Name { fmt.Printf("Index: %d, repo %s found in Allowed as %s\n", id, *repo.Name, i) } else { fmt.Printf("ALARM: repo %s was NOT found in Allowed!\n", *repo.Name) } } } ...
Run
$ go run go-github-public-repos-checker.go
Allowed repos: [1 2]
ALARM: repo org-repo-1-pub was NOT found in Allowed!
ALARM: repo org-repo-2-pub was NOT found in Allowed!
看起来它奏效了吗?
名称为 1
和 2
的存储库 != org-repo-1-pub
和 org-repo-2-pub
。
但当进行“反向检查”时,即当我设置 $ALLOWED_REPOS
为实际名称以获得 OK 结果时,一些读者可能已经注意到的问题出现了。
$ export ALLOWED_REPOS="org-repo-1-pub org-repo-2-pub"
Check
$ go run go-github-public-repos-checker.go
Allowed repos: [org-repo-1-pub org-repo-2-pub]
ALARM: repo org-repo-1-pub was NOT found in Allowed!
ALARM: repo org-repo-2-pub was NOT found in Allowed!
为什么?
因为在 allowedRepos
中,我们没有一个列表,比如 slice 和 Go——而只是一个 string
。
添加 i
变量的输出和索引号
... for r_id, repo := range repos { for a_id, i := range allowedRepos { fmt.Printf("ID: %d Type: %T Value: %s\n", a_id, allowedRepos, i) if i == *repo.Name { fmt.Printf("Index: %d, repo %s found in Allowed as %s\n", r_id, *repo.Name, i) } else { fmt.Printf("ALARM: repo %s was NOT found in Allowed!\n", *repo.Name) } } } ...
Check
$ go run go-github-public-repos-checker.go
Allowed repos: [org-repo-1-pub org-repo-2-pub]
ID: 0 Type: []string Value: org-repo-1-pub org-repo-2-pub
ALARM: repo org-repo-1-pub was NOT found in Allowed!
ID: 0 Type: []string Value: org-repo-1-pub org-repo-2-pub
ALARM: repo org-repo-2-pub was NOT found in Allowed!
allowedRepos
确实是一个列表,但只有一个 0
元素,它保存着“org-repo-1-pub org-repo-2-pub
”值。
第二次尝试
为了让它工作——我们需要将 allowedRepos
转换为一个真正的列表。
让我们使用 strings
包
... // allowedRepos := []string{os.Getenv("ALLOWED_REPOS")} allowedRepos := strings.Fields(os.Getenv("ALLOWED_REPOS")) fmt.Printf("Allowed repos: %s\n", allowedRepos) for r_id, repo := range repos { for a_id, i := range allowedRepos { fmt.Printf("ID: %d Type: %T Value: %s\n", a_id, allowedRepos, i) if i == *repo.Name { fmt.Printf("Index: %d, repo %s found in Allowed as %s\n", r_id, *repo.Name, i) } else { fmt.Printf("ALARM: repo %s was NOT found in Allowed!\n", *repo.Name) } } } ...
allowedRepos
现在将由 strings.Fields()
填充。
检查结果
$ go run go-github-public-repos-checker.go
Allowed repos: [org-repo-1-pub org-repo-2-pub]
ID: 0 Type: []string Value: org-repo-1-pub
Index: 0, repo org-repo-1-pub found in Allowed as org-repo-1-pub
ID: 1 Type: []string Value: org-repo-2-pub
ALARM: repo org-repo-1-pub was NOT found in Allowed!
ID: 0 Type: []string Value: org-repo-1-pub
ALARM: repo org-repo-2-pub was NOT found in Allowed!
ID: 1 Type: []string Value: org-repo-2-pub
Index: 1, repo org-repo-2-pub found in Allowed as org-repo-2-pub
好的——现在好多了,但这也行不通,因为有一些“误报”结果,因为每个 repos
元素都与每个 allowedRepos
元素进行比较。
第三次尝试
让我们重写它,现在我们尝试使用 repo
的索引来选择 allowedRepos
中的一个元素
... for r_id, repo := range repos { fmt.Printf("%d %s\n", r_id, *repo.Name) if *repo.Name != allowedRepos[r_id] { fmt.Printf("ALARM: repo %s was NOT found in Allowed!\n", *repo.Name) } else { fmt.Printf("Repo %s found in Allowed\n", *repo.Name) } } ...
也就是说,我们从 repos
中获取元素的 ID,然后检查具有相同 ID 的 allowedRepos
中的元素。
Run
$ go run go-github-public-repos-checker.go
Allowed repos: [org-repo-1-pub org-repo-2-pub]
0 org-repo-1-pub
Repo org-repo-1-pub found in Allowed
1 org-repo-2-pub
Repo org-repo-2-pub found in Allowed
好!
但现在可能会发生另一个问题……
如果两个列表的顺序不同怎么办?
替换
$ export ALLOWED_REPOS="org-repo-1-pub org-repo-2-pub"
存储库名称顺序颠倒
$ export ALLOWED_REPOS="org-repo-2-pub org-repo-1-pub"
Check
$ go run go-github-public-repos-checker.go
Allowed repos: [org-repo-2-pub org-repo-1-pub]
0 org-repo-1-pub
ALARM: repo org-repo-1-pub was NOT found in Allowed!
1 org-repo-2-pub
ALARM: repo org-repo-2-pub was NOT found in Allowed!
糟了!
第四次尝试
下一个想法是使用 reflect
模块及其 DeepEqual()
函数。
添加一个名为 actualRepos
的新列表,使用 append
添加从 GitHub 获取的存储库,然后进行比较。
... allowedRepos := strings.Fields(os.Getenv("ALLOWED_REPOS")) var actualRepos []string for _, repo := range repos { actualRepos = append(actualRepos, *repo.Name) } fmt.Printf("Allowed: %s\n", allowedRepos) fmt.Printf("Actual: %s\n", actualRepos) fmt.Println("Slice equal: ", reflect.DeepEqual(allowedRepos, actualRepos)) ...
Run
$ go run go-github-public-repos-checker.go
Allowed: [org-repo-2-pub org-repo-1-pub]
Actual: [org-repo-1-pub org-repo-2-pub]
Slice equal: false
又不行……虽然如果恢复顺序——它会起作用。
$ export ALLOWED_REPOS="org-repo-1-pub org-repo-2-pub"
go run go-github-public-repos-checker.go
Allowed: [org-repo-1-pub org-repo-2-pub]
Actual: [org-repo-1-pub org-repo-2-pub]
Slice equal: true
所以,我们需要回到循环变体,但要以某种方式检查 repo
值在整个 allowedRepos
列表中的存在性,并且只有在不存在时——才创建警报。
第五次尝试
解决方案是:创建一个专用函数,该函数将循环检查所有 allowedRepos
元素,并在找到该值时返回 true
,否则返回 false
。然后——我们可以在 main()
的循环中使用它。
试试吧。
创建一个 isAllowedRepo()
函数,它接受两个参数——要检查的存储库名称和允许的存储库列表,并返回一个布尔值。
... func isAllowedRepo(repoName string, allowedRepos []string) bool { for _, i := range allowedRepos { if i == repoName { return true } } return false } ...
然后在 main()
中——遍历 repos
的所有元素,逐个传递给 isAllowedRepo()
,然后打印结果。
... for _, repo := range repos { fmt.Printf("Checking %s\n", *repo.Name) fmt.Println(isAllowedRepo(*repo.Name, allowedRepos)) } ...
我们来测试一下。
首先,恢复初始顺序的允许存储库列表
$ export ALLOWED_REPOS="org-repo-1-pub org-repo-2-pub"
go run go-github-public-repos-checker.go
Checking org-repo-1-pub
true
Checking org-repo-2-pub
true
很好!
然后是反向顺序
$ export ALLOWED_REPOS="org-repo-2-pub org-repo-1-pub"
go run go-github-public-repos-checker.go
Checking org-repo-1-pub
true
Checking org-repo-2-pub
true
仍然有效……
现在移除一个允许的存储库
$ export ALLOWED_REPOS="org-repo-2-pub"
go run go-github-public-repos-checker.go
Checking org-repo-1-pub
false
Checking org-repo-2-pub
true
太棒了!
现在当我们得到 false
时——我们可以发出警报。
整个代码现在。出于历史原因,保留“原样”。
package main import ( "os" "context" "fmt" _"reflect" "strings" "github.com/google/go-github/github" ) func isAllowedRepo(repoName string, allowedRepos []string) bool { for _, i := range allowedRepos { if i == repoName { return true } } return false } func main() { client := github.NewClient(nil) opt := &github.RepositoryListByOrgOptions{Type: "public"} repos, _, _ := client.Repositories.ListByOrg(context.Background(), os.Getenv("GITHUB_ORG_NAME"), opt) // allowedRepos := []string{os.Getenv("ALLOWED_REPOS")} allowedRepos := strings.Fields(os.Getenv("ALLOWED_REPOS")) // var actualRepos []string /* for _, repo := range repos { actualRepos = append(actualRepos, *repo.Name) } fmt.Printf("Allowed: %s\n", allowedRepos) fmt.Printf("Actual: %s\n", actualRepos) fmt.Println("Slice equal: ", reflect.DeepEqual(allowedRepos, actualRepos)) */ for _, repo := range repos { fmt.Printf("Checking %s\n", *repo.Name) fmt.Println(isAllowedRepo(*repo.Name, allowedRepos)) } /* for r_id, repo := range repos { for _, i := range allowedRepos { fmt.Printf("Checking %s and %s\n", *repo.Name, i) if i == *repo.Name { fmt.Printf("Index: %d, repo %s found in Allowed as %s\n", r_id, *repo.Name, i) break } else { fmt.Printf("ALARM: repo %s was NOT found in Allowed!\n", *repo.Name) } } } */ /* for r_id, repo := range repos { fmt.Printf("%d %s\n", r_id, *repo.Name) if *repo.Name != allowedRepos[r_id] { fmt.Printf("ALARM: repo %s was NOT found in Allowed!\n", *repo.Name) } else { fmt.Printf("Repo %s found in Allowed\n", *repo.Name) } } */ /* for r_id, repo := range repos { for a_id, i := range allowedRepos { fmt.Printf("ID: %d Type: %T Value: %s\n", a_id, allowedRepos, i) if i == *repo.Name { fmt.Printf("Index: %d, repo %s found in Allowed as %s\n", r_id, *repo.Name, i) } else { fmt.Printf("ALARM: repo %s was NOT found in Allowed!\n", *repo.Name) } } } */ }
Golang Slack
最后一件事情是添加 Slack 通知。
这里使用 ashwanthkumar/slack-go-webhook
。
配置 Slack 的 WebHook,获取其 URL。
添加一个新的 sendSlackAlarm()
函数,它接受两个参数——存储库名称和它的 URL。
... func sendSlackAlarm(repoName string, repoUrl string) { webhookUrl := os.Getenv("SLACK_URL") text := fmt.Sprintf(":scream: ALARM: repository *%s* was NOT found in Allowed!", repoName) attachment := slack.Attachment{} attachment.AddAction(slack.Action{Type: "button", Text: "RepoURL", Url: repoUrl, Style: "danger"}) payload := slack.Payload{ Username: "Github checker", Text: text, Channel: os.Getenv("SLACK_CHANNEL"), IconEmoji: ":scream:", Attachments: []slack.Attachment{attachment}, } err := slack.Send(webhookUrl, "", payload) if len(err) > 0 { fmt.Printf("error: %s\n", err) } } ...
如果 isAllowedRepo()
返回 false
,则将 sendSlackAlarm()
执行添加到 main()
。
... for _, repo := range repos { fmt.Printf("\nChecking %s\n", *repo.Name) if isAllowedRepo(*repo.Name, allowedRepos) { fmt.Printf("OK: repo %s found in Allowed\n", *repo.Name) } else { fmt.Printf("ALARM: repo %s was NOT found in Allowed!\n", *repo.Name) sendSlackAlarm(*repo.Name, *repo.HTMLURL) } } ...
repo.HTMLURL
我们从 go doc github.Repository
中找到的。
添加 $SLACK_URL
和 $SLACK_CHANNEL
环境变量。
$ export SLACK_URL="https://hooks.slack.com/services/T1641GRB9/BA***WRE"
$ export SLACK_CHANNEL="#general"
恢复反向顺序的完整存储库列表。
$ export ALLOWED_REPOS="org-repo-2-pub org-repo-1-pub"
Check
$ go run go-github-public-repos-checker.go
Checking org-repo-1-pub
OK: repo org-repo-1-pub found in Allowed
Checking org-repo-2-pub
OK: repo org-repo-2-pub found in Allowed
好的……
移除一个允许的。
$ export ALLOWED_REPOS="org-repo-2-pub"
再次检查
$ go run go-github-public-repos-checker.go
Checking org-repo-1-pub
ALARM: repo org-repo-1-pub was NOT found in Allowed!
Checking org-repo-2-pub
OK: repo org-repo-2-pub found in Allowed
还有 Slack 通知
完成!
该脚本可在 setevoy-tools Github 存储库中找到。
类似帖子
历史
- 2019 年 4 月 15 日:初始版本