Go: checking public repositories list in Github. Go slices comparison. The first Golang experience.
Tags: go, programming, beginners
Published: 2019-04-13 14:08:00

---
title: Go: checking public repositories list in Github. Go slices comparison. The first Golang experience.
published: true
tags: Golang,programming,beginners
canonical_url: https://rtfm.co.ua/en/go-checking-public-repositories-list-in-github-go-slices-comparison-the-first-golang-experience/
---

![](https://rtfm.co.ua/wp-content/uploads/2018/04/golang-color-icon2.png)The task is to write a tool which will be started from a Jenkin’s job by a cron and will check an organization’s public repositories list in the Github.

A Docker-image build and a Jenkins job are described in the [Jenkins: a job to check a Github organization’s public repositories list](https://rtfm.co.ua/en/jenkins-a-job-to-check-a-github-organizations-public-repositories-list/) post.

Then it has to compare received repositories list with a predefined list with allowed repositories and if they will not be equal – send an alert to a Slack channel.

The idea is to have such a check in case if developers accidentally will create a public repository instead of private or will change a private repository to the public and to get a notification about such issue.

It could be written with `bash` and `curl`, or Python with `urllib` and use Github API directly, but I want to get some Go experience so will use it.

In fact – this is my the very first self-written Golang code which I didn’t use a lot and even don’t know its syntax, but will try to use some existing knowledge with C/Python, add some logic and of course with the Google’s help and write something workable.

To work with API Github the [`go-github`](https://github.com/google/go-github) package will be used.

Let’s begin.

Add imports, the function was created automatically with the [`vim-go`](https://rtfm.co.ua/vim-prevrashhaem-redaktor-v-ide-plaginy-i-vot-eto-vot-vsyo/) plugin:

```
import (
    "fmt"
    "github.com/google/go-github/github"
)

func main() {
    fmt.Println("vim-go")
}
```

Add `$GOPATH`:

```
$ sudo mkdir /usr/local/go
$ sudo chown setevoy:setevoy /usr/local/go/
$ export GOPATH=/usr/local/go && export GOBIN=/usr/local/go/bin
```

Install package:

```
$ go get
/home/setevoy/Scripts/Go/GTHB
./gitrepos.go:5:2: imported and not used: "github.com/google/go-github/github"
```

### Getting repositories list from Github

Copy and paste the first example from the package’s [README](https://github.com/google/go-github/blob/master/README.md), an organization name will be taken from the `$GITHUB_ORG_NAME`and let’s try get something.

Set the variable:

```
$ export GITHUB_ORG_NAME="rtfmorg"
```

And code:

```
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)
}
```

In the original example, organization name was hardcoded but here we will take it from a variable which will be set in a Jenkin’s job using [`os.Getenv`](https://golang.org/pkg/os/#Getenv).

Add `context` and `os` imports.

Run the code:

```
$ 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
```

Errr…

Okay.

I thought `client.Repositories.ListByOrg` will return actually list – just because of the _List_ in its name :-)

Check what data type we have in the `repos` object. Use `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
```

Good… It’s really list (`[]`), apparently pointed to a structure – `github.Repository`.

Let’s check with the `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"`
...
```

Yup – it’s structure and also we see all its fields.

To disable Go's “_imported and not used: “reflect”_ message – set it as `_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)
    }
}
```

Here from the `repos` list we are getting an element’s ID and its value.

Can add ID’s as well:

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

### Comparing lists in Go

Now need to add another one list which will keep repositories list from Github.

Allowed to be public repositories will be passed from Jenkins in a list view separated by spaces:

```
$ export ALLOWED_REPOS="1 2"
```

#### Attempt number one

Create `allowedRepos` of the `string` type and save allowed public repositories list in it:

```
...
    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) 
...
```

But here is an issue which I faced with a bit later when started comparing lists. Will see it shortly.

Run the code:

```
$ go run go-github-public-repos-checker.go
0 Repo: org-repo-1-pub
1 Repo: org-repo-2-pub
Allowed repos: [1 2]
```

Okay – it works.

Now need to make checks between two repositories lists – `repos` with repositories from Github and `allowedRepos`.

The first solution was next:

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

Looks like it works?

Repositories with the _1_ and _2_ names != _org-repo-1-pub_ and _org-repo-2-pub_.

But the problem which might be already obvious for some readers appeared when I did a “back-check”, i.e. when I set `$ALLOWED_REPOS` with real names to get the OK result:

```
$ 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!
```

_And why_?

Because of in the `allowedRepos` we have not a list aka [slice and Go](https://blog.golang.org/go-slices-usage-and-internals) – but just a string.

Let’s add the `i` variable’s output and indexes numbers:

```
...
    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` is really a list but with the only _0_ element which keeps the “_org-repo-1-pub org-repo-2-pub_” value.

#### Attempt number two

To make this working – need to convert the `allowedRepos` to a real list.

Let’s use the [`strings`](https://golang.org/pkg/strings/) package:

```
...
    // 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` now will be filled with the [`strings.Fields()`](https://golang.org/pkg/strings/#Fields).

Check result:

```
$ 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
```

Okay – much better now but this also will not work as there some “false-positive” results because of each `repos`‘s element is compared with each `allowedRepos`‘s element.

#### Attempt number three

Let’s rewrite it and now let’s try to use a `repo`‘s indexes to chose an element from the `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)
        }
    }
...
```

I.e. from the `repos` we getting an element’s ID and then checking the `allowedRepos`‘s element with the same ID.

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
```

Nice!

But another issue can happen now…

What if the order in both lists will differ?

Set instead of the:

```
$ export ALLOWED_REPOS="org-repo-1-pub org-repo-2-pub"
```

Repositories names in the reversed order:

```
$ 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!
```

D’oh!

#### Attempt number four

The next idea was to use the `reflect` module and its [`DeepEqual()`](https://golang.org/pkg/reflect/#DeepEqual) function.

Add one more list called `actualRepos`, add repositories taken from Github with the [`append`](https://tour.golang.org/moretypes/15) and then compare them:

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

And again no… Although if revert order back – it will work:

```
$ 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
```

So, need to go back to the loop’s variant but to check somehow`repo`‘s value in the whole `allowedRepos` list and only if it’s absent – create an alert.

#### Attempt number five

The solution was next: create a dedicated function which will check all `allowedRepos`‘s elements in a loop and will return `true` if the value will be found and `false` otherwise. Then – we can use this in the`main()`‘s loop.

Let’s try.

Create a `isAllowedRepo()`function which will accept two arguments – a repository name to be checked and the allowed repositories list and will return a boolean value:

```
...
func isAllowedRepo(repoName string, allowedRepos []string) bool {
    for _, i := range allowedRepos {
        if i == repoName {
            return true
        }
    }
    return false
}
...
```

Then in the `main()` – run a loop over all `repos`‘s elements, pass them one by one to the `isAllowedRepo()` and then print a result:

```
...
    for _, repo := range repos {
        fmt.Printf("Checking %s\n", *repo.Name)
        fmt.Println(isAllowedRepo(*repo.Name, allowedRepos))
    }
...
```

Let’s test.

First, restore allowed repositories list in the initial order:

```
$ 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
```

Good!

And reversed order:

```
$ 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
```

Still works…

Now remove one of the allowed repositories:

```
$ 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
```

Great!

Now when we will get `false` – we can raise an alert.

The whole code now. Leaving “as is” just for history:

```
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

And the final thing is to add a Slack notification.

Use [`ashwanthkumar/slack-go-webhook`](https://github.com/ashwanthkumar/slack-go-webhook) here.

Configure Slack’s WebHook, get its URL.

Add a new `sendSlackAlarm()` function which will accept two arguments – repository name and its 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)
    }
}
...
```

Add the `sendSlackAlarm()` execution  to the `main()` if the `isAllowedRepo()` returned `false`:

```
...
    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` we found from the `go doc github.Repository`.

Add `$SLACK_URL` and `$SLACK_CHANNEL` environment variables:

```
$ export SLACK_URL="https://hooks.slack.com/services/T1641GRB9/BA***WRE"
$ export SLACK_CHANNEL="#general"
```

Restore the full repositories list on reversed order:

```
$ 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
```

Okay…

Remove one allowed:

```
$ export ALLOWED_REPOS="org-repo-2-pub"
```

Check again:

```
$ 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
```

And Slack notification:

[![](https://rtfm.co.ua/wp-content/uploads/2019/04/Screenshot_20190413_142710.png "Go: проверка списка публичных репозиториев в Github и уведомления в Slack. Первый опыт с Golang.")](https://rtfm.co.ua/wp-content/uploads/2019/04/Screenshot_20190413_142710.png)

Done.

The script is available in the [_setevoy-tools_](https://github.com/setevoy2/setevoy-tools/blob/master/go-github-public-repos-checker/go-github-public-repos-checker.go) Github repository.

### Similar posts
 
- <small>04/08/2019</small> [Atom: useful plugins](https://dev.to/setevoy/atom-useful-plugins-179b)<small> (0)</small>  
- <small>02/12/2019</small> [Golang: unrecognized import path “math/bits” (import path does not begin with hostname)](https://rtfm.co.ua/golang-unrecognized-import-path-math-bits-import-path-does-not-begin-with-hostname/)<small> (0)</small>  
- <small>02/14/2019</small> [Golang: Go in Practice – заметки на полях, часть 2 – CLI приложение на Go](https://rtfm.co.ua/golang-go-in-practice-zametki-na-polyax-chast-2-cli-prilozhenie-na-go/)<small> (0)</small>