Skip to content

Commit

Permalink
Init repo
Browse files Browse the repository at this point in the history
  • Loading branch information
Stein Fletcher committed Dec 17, 2017
0 parents commit a478b1f
Show file tree
Hide file tree
Showing 13 changed files with 781 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/src/
github-team-clone
/vendor/
51 changes: 51 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"


[[constraint]]
name = "github.com/stretchr/testify"
version = "1.1.4"

[[constraint]]
name = "github.com/urfave/cli"
version = "1.20.0"

[[constraint]]
name = "gopkg.in/h2non/gock.v1"
version = "1.0.6"
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# github-team-clone

A simple cli app to clone all repos managed by a github team

## Install

go install github.com/steinfletcher/github-team-clone

## Usage

github-team-clone -o MyOrg -t MyTeam

```bash
NAME:
github-team-clone - clone github team repos

USAGE:
$ github-team-clone -o MyOrg -t MyTeam

VERSION:
0.0.1

DESCRIPTION:
A simple cli to clone all the repos managed by a github team

AUTHOR:
Stein Fletcher

COMMANDS:
help, h Shows a list of commands or help for one command

GLOBAL OPTIONS:
--org value, -o value github organisation
--team value, -t value github team
--username value, -u value github username [$GITHUB_USER, $GITHUB_USERNAME]
--token value, -k value github personal access token [$GITHUB_TOKEN, $GITHUB_API_KEY, $GITHUB_PERSONAL_ACCESS_TOKEN]
--dir value, -d value directory to clone into (default: "src")
--help, -h show help
--version, -v print the version
```
102 changes: 102 additions & 0 deletions github/github.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package github

import (
"net/http"
"fmt"
"io/ioutil"
"time"
"encoding/json"
"strings"
"errors"
)

func NewGithub(username string, apiToken string) Github {
return &githubCli{username, apiToken}
}

type githubCli struct {
username string
apiToken string
}

type Github interface {
OrganisationTeams(org string) (error, []Team)
TeamRepos(teamId int) (error, []Repo)
}

type Team struct {
Id int `json:"id"`
Name string `json:"name"`
}

type Repo struct {
SshUrl string `json:"ssh_url"`
Name string `json:"name"`
}

func (g *githubCli) OrganisationTeams(org string) (error, []Team) {
err, resp := doGet(fmt.Sprintf("https://api.github.com/orgs/%s/teams", org), g.username, g.apiToken)
defer resp.Body.Close()
if err != nil {
return err, nil
}
teams := make([]Team, 0)
bytes, _ := ioutil.ReadAll(resp.Body)
json.Unmarshal(bytes, &teams)
return nil, teams
}

func (g *githubCli) TeamRepos(teamId int) (error, []Repo) {
var page = 1
var repos []Repo
for {
err, res := doGet(fmt.Sprintf("https://api.github.com/teams/%d/repos?page=%d", teamId, page), g.username, g.apiToken)
if err != nil {
return err, nil
}

bytes, err := ioutil.ReadAll(res.Body)
if err != nil {
return err, nil
}
result := make([]Repo, 0)
json.Unmarshal(bytes, &result)
repos = append(repos, result...)

if hasNextPage(res) {
page++
} else {
break
}
}
return nil, repos
}

func doGet(url string, username string, apiToken string) (error, *http.Response) {
httpClient := http.Client{Timeout: time.Second * 5}
request, _ := http.NewRequest(http.MethodGet, fmt.Sprintf(url), nil)
request.SetBasicAuth(username, apiToken)

response, err := httpClient.Do(request)

if err != nil {
return err, nil
}

if response.StatusCode > 200 && response.StatusCode < 300 {
return errors.New(fmt.Sprintf("Error, github returned status=%s", response.StatusCode)), nil
}

return nil, response
}

func hasNextPage(response *http.Response) bool {
h := response.Header.Get("link")
pageLinks := strings.Split(h, ",")
for _, link := range pageLinks {
if strings.Contains(link, "rel=\"next\"") {
return true
}
}
return false
}
55 changes: 55 additions & 0 deletions github/github_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package github

import (
"testing"
"github.com/stretchr/testify/assert"
"gopkg.in/h2non/gock.v1"
)

func TestFetchTeams(t *testing.T) {
defer gock.Off()

gock.New("https://api.github.com").
MatchHeader("Authorization", "Basic dXNlcm5hbWU6cGFzc3dvcmQ=").
Get("/orgs/MyOrg/teams").
Reply(200).
File("../testdata/teamsResponse.json")

githubCli := NewGithub("username", "password")

_, teams := githubCli.OrganisationTeams("MyOrg")

assert.Equal(t, teams[0].Name, "Winners")
assert.Equal(t, teams[0].Id, 2285788)
}

func TestFetchTeamRepos(t *testing.T) {
defer gock.Off()

teamReposResp("1", "<https://api.github.com/teams/2285789/repos?page=2>; rel=\"next\", <https://api.github.com/teams/2285789/repos?page=3>; rel=\"last\"", "teamReposResponsePage1.json")
teamReposResp("2", "<https://api.github.com/teams/2285789/repos?page=1>; rel=\"prev\", <https://api.github.com/teams/2285789/repos?page=3>; rel=\"next\", <https://api.github.com/teams/2285789/repos?page=1>; rel=\"first\", <https://api.github.com/teams/2285789/repos?page=3>; rel=\"last\"", "teamReposResponsePage2.json")
teamReposResp("3", "<https://api.github.com/teams/2285789/repos?page=2>; rel=\"prev\", <https://api.github.com/teams/2285789/repos?page=1>; rel=\"first\"", "teamReposResponsePage3.json")

githubCli := NewGithub("username", "password")

_, teams := githubCli.TeamRepos(2285789)

var repos []string
for _, team := range teams {
repos = append(repos, team.Name)
}

assert.Contains(t, repos, "some-repo1")
assert.Contains(t, repos, "some-repo2")
assert.Contains(t, repos, "some-repo3")
}

func teamReposResp(page string, linkHeader string, file string) {
gock.New("https://api.github.com").
MatchHeader("Authorization", "Basic dXNlcm5hbWU6cGFzc3dvcmQ=").
Get("/teams/2285789/repos").
MatchParam("page", page).
Reply(200).
SetHeader("Link", linkHeader).
File("../testdata/" + file)
}
87 changes: 87 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package main

import (
"os"
"github.com/urfave/cli"
"github.com/steinfletcher/github-team-clone/github"
"github.com/steinfletcher/github-team-clone/teamclone"
"github.com/apex/log"
)

func main() {
app := cli.NewApp()
app.Author = "Stein Fletcher"
app.Name = "github-team-clone"
app.Usage = "clone github team repos"
app.UsageText = "github-team-clone -o MyOrg -t MyTeam"
app.Version = "0.0.1"
app.EnableBashCompletion = true
app.Description = "A simple cli to clone all the repos managed by a github team"

app.Flags = []cli.Flag {
cli.StringFlag{
Name: "org, o",
Usage: "github organisation",
},
cli.StringFlag{
Name: "team, t",
Usage: "github team",
},
cli.StringFlag{
Name: "username, u",
Usage: "github username",
EnvVar: "GITHUB_USER,GITHUB_USERNAME",
},
cli.StringFlag{
Name: "token, k",
Usage: "github personal access token",
EnvVar: "GITHUB_TOKEN,GITHUB_API_KEY,GITHUB_PERSONAL_ACCESS_TOKEN",
},
cli.StringFlag{
Name: "dir, d",
Usage: "directory to clone into",
Value: "src",
},
}

app.Action = func(c *cli.Context) error {
username := c.String("username")
token := c.String("token")
team := c.String("team")
org := c.String("org")
dir := c.String("dir")

if len(username) == 0 {
die("env var GITHUB_USERNAME or flag -u must be set", c)
}

if len(token) == 0 {
die("env var GITHUB_TOKEN or flag -k must be set", c)
}

if len(team) == 0 {
die("github team (-t) not set", c)
}

if len(org) == 0 {
die("github organisation (-o) not set", c)
}

githubCli := github.NewGithub(username, token)
cloner := teamclone.NewCloner(githubCli, dir)

err := cloner.CloneTeamRepos(org, team)
if err != nil {
return cli.NewExitError(err.Error(), 1)
}

return nil
}

app.Run(os.Args)
}

func die(msg string, c *cli.Context) {
cli.ShowAppHelp(c)
log.Fatal(msg)
}
Loading

0 comments on commit a478b1f

Please sign in to comment.