65.9K
CodeProject 正在变化。 阅读更多。
Home

Go:检查 GitHub 上的公共存储库列表。Go 切片比较。第一次 Golang 体验。

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2019 年 4 月 15 日

CPOL

5分钟阅读

viewsIcon

3703

将由 Jenkins 的 cron 作业启动的工具,用于检查 GitHub 上某个组织的公共存储库列表

引言

任务是编写一个工具,该工具将由 Jenkins 的 cron 作业启动,并检查 GitHub 上某个组织的公共存储库列表。

然后,它需要将收到的存储库列表与预定义的允许存储库列表进行比较,如果它们不相等,则向 Slack 频道发送警报。

这样做的目的是在开发人员意外地将私有存储库创建为公共存储库,或者将公共存储库更改为私有存储库时,能够进行此类检查,并收到有关此问题的通知。

可以使用 bashcurl,或 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 设置。

添加 contextos 导入。

运行代码

$ 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 的 reposallowedRepos——之间进行检查。

第一个解决方案是这样的

...
    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!

看起来它奏效了吗?

名称为 12 的存储库 != org-repo-1-puborg-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 日:初始版本
© . All rights reserved.