Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support autodiscovery #46

Merged
merged 2 commits into from
Mar 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.19'
go-version: '1.21'

- name: Get Build Tools
run: |
Expand Down Expand Up @@ -46,7 +46,7 @@ jobs:
- name: install go
uses: actions/setup-go@v5
with:
go-version: '1.19'
go-version: '1.21'

- name: git checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-please.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- run: git fetch --force --tags
- uses: actions/setup-go@v5
with:
go-version: "1.20"
go-version: "1.21"
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
with:
Expand Down
62 changes: 40 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,30 @@
Application to check Github for Pull Requests, that are not Drafts, in repos the user cares about.

## Usage
The program takes no arguments, and is configured via ENV variables.
The program is configured via a YAML file located at `$HOME/.config/ghreport/config.yaml`.

* ghreportToken: Should be set to a Github API Token with access to the repos you are checking
* token: Should be set to a Github API Token with access to the repos you are checking
* Set permissions for token to repo - full control of private repositories, enable SSO if your repos require it
* ![Github Personal Access Token Permissions](https://github.com/Jmainguy/ghreport/blob/main/docs/permissions.png?raw=true)
* subscribedRepos: Should be set to a space delimmited list of Github Repos you want to check
* One of subscribedRepos, autoDiscover.organizations, or autoDiscover.users must be set if you wish to have any results. You can set all three if you wish.
* topic limits what is returned from organizations and users to just that topic, this is an optional field.

Example configuration in ~/.bashrc
```
export ghreportToken=e0e9eac4e84446df6f3db180d07bfb222e91234
export subscribedRepos="Jmainguy/ghreport Jmainguy/bible Jmainguy/ghReview Jmainguy/bak"
```
Here's an example configuration:

Additionally if you have a long list of repos to watch you can use this format when setting the environment variable:
```
export subscribedRepos="\
somesite/aebot \
somesite/ansible-okta-aws-auth \
somesite/blahblah"
```yaml
autoDiscover:
organizations:
- name: your_organization_name
topic: topic_to_watch
users:
- name: your_username
topic: topic_to_watch
subscribedRepos:
- Jmainguy/ghreport
- Jmainguy/bible
- Jmainguy/ghReview
- Jmainguy/bak
token: e0e9eac4e84446df6f3db180d07bfb222e91234
```

Running the progam
Expand All @@ -32,14 +37,27 @@ ghreport
Sample output

```
[jmainguy@jmainguy-7410 ghreport]$ ghreport
https://github.com/Jmainguy/stockop/pull/9: createdAt 2023-04-07 01:10:22 +0000 UTC
```

## Linux / macOS homebrew install

```/bin/bash
brew install jmainguy/tap/ghreport
jmainguy@fedora:~/Github/ghreport$ ./ghreport
https://github.com/Jmainguy/statuscode/pull/32
author: renovate
Age: 3 days
reviewDecision: ❌
mergeable ✅
https://github.com/Jmainguy/statuscode/pull/33
author: renovate
Age: 3 days
reviewDecision: ✅
mergeable ✅
https://github.com/Standouthost/Multicraft/pull/9
author: TheWebGamer
Age: 3321 days
reviewDecision: ✅
mergeable ❌
https://github.com/Standouthost/Multicraft/pull/28
author: ungarscool1
Age: 2700 days
reviewDecision: 😅
mergeable ✅
```

## Releases
Expand Down
129 changes: 129 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package main

import (
"fmt"
"os"
"path/filepath"
"strings"

"gopkg.in/yaml.v2"
)

// Config : configuration for what orgs / users / repos and token to use
type Config struct {
AutoDiscover struct {
Organizations []struct {
Name string `yaml:"name"`
Topic string `yaml:"topic"`
} `yaml:"organizations"`
Users []struct {
Name string `yaml:"name"`
Topic string `yaml:"topic"`
} `yaml:"users"`
} `yaml:"autoDiscover"`
SubscribedRepos []string `yaml:"subscribedRepos"`
Token string `yaml:"token"`
}

func ensureConfigDirExists(configDir string) error {
// Check if config directory exists
_, err := os.Stat(configDir)
if os.IsNotExist(err) {
// Config directory doesn't exist, create it
err := os.MkdirAll(configDir, 0700) // 0700 means only the owner can read, write, and execute
if err != nil {
return fmt.Errorf("failed to create config directory: %v", err)
}
} else if err != nil {
// Some error occurred while checking the existence of the directory
return fmt.Errorf("failed to check config directory: %v", err)
}

return nil
}

func ensureConfigFileExists(configFilePath string) error {
_, err := os.Stat(configFilePath)
if os.IsNotExist(err) {
// Config file doesn't exist, create it with default values
defaultConfig := Config{
SubscribedRepos: []string{},
Token: "",
}
configBytes, err := yaml.Marshal(defaultConfig)
if err != nil {
return fmt.Errorf("failed to marshal default config: %v", err)
}
err = os.WriteFile(configFilePath, configBytes, 0600) // 0600 means only the owner can read and write
if err != nil {
return fmt.Errorf("failed to create config file: %v", err)
}
} else if err != nil {
// Some error occurred while checking the existence of the file
return fmt.Errorf("failed to check config file: %v", err)
}

return nil
}

func getConfigFilePath() (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", err
}
configDir := filepath.Join(homeDir, ".config/ghreport")
if err := ensureConfigDirExists(configDir); err != nil {
return "", err
}
configFilePath := filepath.Join(configDir, "config.yaml")
if err := ensureConfigFileExists(configFilePath); err != nil {
return "", err
}
return configFilePath, nil
}

func readConfigFile(configFilePath string) (*Config, error) {
file, err := os.Open(configFilePath)
if err != nil {
return nil, err
}
defer file.Close()

var config Config
decoder := yaml.NewDecoder(file)
if err := decoder.Decode(&config); err != nil {
return nil, err
}

return &config, nil
}

func getConfig() (*Config, error) {
// Check if config file exists
configFilePath, err := getConfigFilePath()
if err == nil {
if _, err := os.Stat(configFilePath); err == nil {
// Config file exists, read values from there
config, err := readConfigFile(configFilePath)
if err == nil {
return config, nil
}
}
}

// If config file doesn't exist or there was an error reading it, fallback to environment variables
var config Config
envSubscribedRepos := os.Getenv("subscribedRepos")
if envSubscribedRepos == "" {
return &config, fmt.Errorf("env variable subscribedRepos is not defined")
}
config.SubscribedRepos = strings.Split(envSubscribedRepos, " ")
envToken := os.Getenv("ghreportToken")
if envToken == "" {
return &config, fmt.Errorf("env variable ghreportToken is not defined")
}
config.Token = envToken

return &config, nil

}
7 changes: 4 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
module github.com/jmainguy/ghreport

go 1.19
go 1.21

require (
github.com/shurcooL/githubv4 v0.0.0-20240120211514-18a1ae0e79dc
github.com/stretchr/testify v1.9.0
golang.org/x/oauth2 v0.18.0
gopkg.in/yaml.v2 v2.4.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect
github.com/stretchr/objx v0.5.2 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/protobuf v1.32.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
23 changes: 7 additions & 16 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,28 +1,20 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/shurcooL/githubv4 v0.0.0-20240120211514-18a1ae0e79dc h1:vH0NQbIDk+mJLvBliNGfcQgUmhlniWBDXC79oRxfZA0=
github.com/shurcooL/githubv4 v0.0.0-20240120211514-18a1ae0e79dc/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8=
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0=
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
Expand All @@ -32,8 +24,6 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand All @@ -58,10 +48,11 @@ google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAs
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
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.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
54 changes: 29 additions & 25 deletions helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,13 @@ package main
import (
"context"
"fmt"
"os"
"strings"
"time"

"github.com/shurcooL/githubv4"
"golang.org/x/oauth2"
)

func getEnvVariables() ([]string, string, error) {
envSubscribedRepos := os.Getenv("subscribedRepos")
if envSubscribedRepos == "" {
return nil, "", fmt.Errorf("env variable subscribedRepos is not defined")
}

subscribedRepos := strings.Split(envSubscribedRepos, " ")

envToken := os.Getenv("ghreportToken")
if envToken == "" {
return nil, "", fmt.Errorf("env variable ghreportToken is not defined")
}

return subscribedRepos, envToken, nil
}

func createGithubClient(envToken string) *githubv4.Client {
src := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: envToken},
Expand All @@ -49,14 +33,34 @@ func getOwnerAndRepo(ownerRepo string) (string, string, error) {
return ownerAndRepo[0], ownerAndRepo[1], nil
}

func compareSlices(a, b []string) bool {
if len(a) != len(b) {
return false
func formatTimeElapsed(duration time.Duration) string {
if duration.Hours() >= 24 {
return fmt.Sprintf("%.0f days", duration.Hours()/24)
} else if duration.Hours() >= 1 {
return fmt.Sprintf("%.0f hours", duration.Hours())
} else {
return fmt.Sprintf("%.0f minutes", duration.Minutes())
}
for i, v := range a {
if v != b[i] {
return false
}
}

func getReviewDecisionEmoji(decision string) string {
switch decision {
case "APPROVED":
return "✅" // Green checkmark emoji
case "CHANGES_REQUESTED":
return "❌" // Red x emoji
case "REVIEW_REQUIRED":
return "🔍" // Magnifying glass emoji or any other appropriate emoji
default:
return "😅" // Emoji indicating everything is okay, but no review was requested
}
}

func getMergeableEmoji(mergeable string) string {
if mergeable == "MERGEABLE" {
return "✅" // Green checkmark emoji
} else if mergeable == "CONFLICTING" {
return "❌" // Red x emoji
}
return true
return ""
}
Loading
Loading