Skip to content

Commit

Permalink
Merge pull request #13 from kununu/replace-git-library
Browse files Browse the repository at this point in the history
Add PR creation functionality
  • Loading branch information
enkodr authored Aug 28, 2023
2 parents cc7704e + 9aaf4f9 commit c9e38da
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 79 deletions.
73 changes: 63 additions & 10 deletions apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,95 @@ package github

import (
"errors"
"fmt"
"net/http"
"net/url"
"strings"

"github.com/bradleyfalzon/ghinstallation/v2"
"github.com/go-git/go-git/v5"
"github.com/google/go-github/v54/github"
)

// GitHubApp is a struct to interact with GitHub App's API
type GitHubApp struct {
Config *GitHubAppConfig
Auth Auth
gitRepo *git.Repository
worktree *git.Worktree
Config *GitHubAppConfig
Auth Auth
gitClient *git.Repository
githubClient *github.Client
worktree *git.Worktree
}

// Configuration for the GitHub App interaction
type GitHubAppConfig struct {
repoName string
RepoURL string
ApplicationID string
InstallationID string
ApplicationID int64
InstallationID int64
LocalPath string
PrivateKey []byte
PrivateKey string
}

var githubAPIURL string = "https://api.github.com"

// extractRepoName extracts the repository name from the URL
func extractRepoName(gitURL string) (string, error) {
parsedURL, err := url.Parse(gitURL)
if err != nil {
return "", err
}

// Splitting the path based on slashes
parts := strings.Split(parsedURL.Path, "/")

// Ensure at least one segment exists
if len(parts) == 0 {
return "", fmt.Errorf("could not extract repository name")
}

// The last part of the URL is typically 'repo.git', so we need to further process it to extract 'repo'
repoName := parts[len(parts)-1]

// Removing .git extension if present
if strings.HasSuffix(repoName, ".git") {
repoName = repoName[:len(repoName)-len(".git")]
}

return repoName, nil
}

// Creates a new GitHubApp struct configured by GitHubAppConfig
func NewGitHubApp(cfg *GitHubAppConfig) (*GitHubApp, error) {
if cfg.ApplicationID == "" && cfg.InstallationID == "" {
// check if application ID and installation ID are defined
if cfg.ApplicationID == 0 && cfg.InstallationID == 0 {
return nil, errors.New("provide App ID and Installation ID")
}
// set localPath if not passed
if cfg.LocalPath == "" {
cfg.LocalPath = "./"
}

// get new key for github
itr, err := ghinstallation.NewKeyFromFile(http.DefaultTransport, cfg.ApplicationID, cfg.InstallationID, cfg.PrivateKey)
if err != nil {
return nil, err
}

repoName, err := extractRepoName(cfg.RepoURL)
if err != nil {
return nil, err
}
// set repoName
cfg.repoName = repoName

// initialise a new GitHubApp
ghApp := &GitHubApp{
Config: cfg,
Config: cfg,
githubClient: github.NewClient(&http.Client{Transport: itr}),
}

err := ghApp.authenticate()
// authenticate
err = ghApp.authenticate()
if err != nil {
return nil, err
}
Expand Down
10 changes: 5 additions & 5 deletions apps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Uqt+kzgPAoGBAOeI2EoJSOtpHlJgXT5v8qvqCFdu/oiiS/i9d25CkL0AIlT0YJZu
VAeKVKieSln/vQZfuklfdcmREwcn7LiMmU7KeLm5ehsfUAtjT/9c4KOj6+/unrQZ
NAYPACY/P+sO1+RN3UkezoYdbgnperNKSMreQtrxL/0wkDaKdmUWm0ty
-----END RSA PRIVATE KEY-----
`)
`)

func TestNewGitHubApp(t *testing.T) {

Expand All @@ -45,7 +45,7 @@ func TestNewGitHubApp(t *testing.T) {

// Test Case 2: Missing Application ID
cfg = &GitHubAppConfig{
InstallationID: "123",
InstallationID: 123,
}
_, err = NewGitHubApp(cfg)
if err == nil {
Expand All @@ -54,7 +54,7 @@ func TestNewGitHubApp(t *testing.T) {

// Test Case 3: Missing Installation ID
cfg = &GitHubAppConfig{
ApplicationID: "298674",
ApplicationID: 298674,
}
_, err = NewGitHubApp(cfg)
if err == nil {
Expand All @@ -63,8 +63,8 @@ func TestNewGitHubApp(t *testing.T) {

// Test Case 4: Missing Private Key
cfg = &GitHubAppConfig{
ApplicationID: "298674",
InstallationID: "123",
ApplicationID: 298674,
InstallationID: 123,
}
_, err = NewGitHubApp(cfg)
if err == nil {
Expand Down
15 changes: 11 additions & 4 deletions auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"net/http"
"os"
"time"

"github.com/golang-jwt/jwt"
Expand All @@ -25,8 +26,14 @@ func (ghApp *GitHubApp) buildJWTToken() error {
claims["exp"] = time.Now().Add(10 * time.Minute).Unix()
claims["iss"] = ghApp.Config.ApplicationID

// Read file
keyData, err := os.ReadFile(ghApp.Config.PrivateKey)
if err != nil {
return err
}

// Parse RSA private key
key, err := jwt.ParseRSAPrivateKeyFromPEM(ghApp.Config.PrivateKey)
key, err := jwt.ParseRSAPrivateKeyFromPEM(keyData)
if err != nil {
return err
}
Expand All @@ -49,7 +56,7 @@ type TokenResponse struct {
// Gets the access token to authenticate with github
func (ghApp *GitHubApp) GetAccessToken() (string, error) {
// Parse url
url := fmt.Sprintf("%s/app/installations/%s/access_tokens", githubAPIURL, ghApp.Config.InstallationID)
url := fmt.Sprintf("%s/app/installations/%d/access_tokens", githubAPIURL, ghApp.Config.InstallationID)

// Create the request to github's api
req, _ := http.NewRequest("POST", url, nil)
Expand All @@ -68,7 +75,7 @@ func (ghApp *GitHubApp) GetAccessToken() (string, error) {
}

// Read the the response from the server
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
Expand Down
16 changes: 12 additions & 4 deletions auth_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package github

import (
"os"
"testing"
)

func TestBuildJWTToken(t *testing.T) {
// Create temp key file
tmpKeyFile := "/tmp/key.pem"
os.WriteFile(tmpKeyFile, testPrivateKey, 0644)

// Test Case 1: Invalid private key
cfg := &GitHubAppConfig{
ApplicationID: "123",
PrivateKey: []byte("invalid"),
ApplicationID: 123,
PrivateKey: "invalid",
}
ghApp := &GitHubApp{
Config: cfg,
Expand All @@ -20,8 +25,8 @@ func TestBuildJWTToken(t *testing.T) {

// Test Case 2: Valid private key
cfg = &GitHubAppConfig{
ApplicationID: "298674",
PrivateKey: testPrivateKey,
ApplicationID: 298674,
PrivateKey: tmpKeyFile,
}
ghApp = &GitHubApp{
Config: cfg,
Expand All @@ -33,4 +38,7 @@ func TestBuildJWTToken(t *testing.T) {
if ghApp.Auth.JWTToken == "" {
t.Errorf("Empty JWT token")
}

// Cleanup
os.Remove(tmpKeyFile)
}
51 changes: 14 additions & 37 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
package main

import (
"bufio"
"flag"
"fmt"
"os"
"strings"
"strconv"

"github.com/kununu/go-github"
)

var (
appId string
instId string
appId int64
instId int64
key string
)

func init() {
flag.StringVar(&appId, "a", "", "App ID to use for authentication")
flag.StringVar(&key, "k", "", "Path to key file for authentication")
flag.StringVar(&instId, "i", "", "Installation ID that identifies the APP installation ID on GitHub")
flag.Int64Var(&appId, "a", 0, "App ID to use for authentication")
flag.Int64Var(&instId, "i", 0, "Installation ID that identifies the APP installation ID on GitHub")
flag.Parse()
}

func main() {
keyBytes := []byte{}
// Get the values from the environment variables if they are set with parameters
if appId == "" {
appId = os.Getenv("GITHUB_APP_ID")
if appId == 0 {
appIdInt, _ := strconv.ParseInt(os.Getenv("GITHUB_APP_ID"), 10, 64)
appId = appIdInt
}
if instId == "" {
instId = os.Getenv("GITHUB_INST_ID")
if instId == 0 {
instIdInt, _ := strconv.ParseInt(os.Getenv("GITHUB_INST_ID"), 10, 64)
instId = instIdInt
}
if key == "" {
key = os.Getenv("GITHUB_KEY_PATH")
Expand All @@ -45,46 +45,23 @@ func main() {
fmt.Printf("you need to pass the private key either with `-k` parameter or by setting GITHUB_KEY_PATH or even passing through STDIN\n")
os.Exit(1)
}
var lines []string
reader := bufio.NewReader(os.Stdin)
for {
// read line from stdin using newline as separator
line, _ := reader.ReadString('\n')
// if line is empty, break the loop
if len(strings.TrimSpace(line)) == 0 {
break
}
//append the line to a slice
lines = append(lines, line)
keyBytes = append(keyBytes, []byte(line)...)
}
key = "stdin"
}
}

// Verify if the necessary information is set
if appId == "" || instId == "" {
if appId == 0 || instId == 0 {
fmt.Println("You need to define the App ID and the path to the key file")
fmt.Println("by passing the values the -a, -i and -k options or")
fmt.Println("by setting GITHUB_APP_ID, GITHUB_INST_ID and GITHUB_KEY_PATH environment variables.")
os.Exit(0)
}

// Read the key from the file
if key != "stdin" {
var err error
keyBytes, err = os.ReadFile(key)
if err != nil {
fmt.Println("error reading the key file")
os.Exit(1)
}
os.Exit(1)
}

// Create a new GitHubApp
ghApp, err := github.NewGitHubApp(&github.GitHubAppConfig{
ApplicationID: appId,
InstallationID: instId,
PrivateKey: keyBytes,
PrivateKey: key,
})
if err != nil {
fmt.Println(err.Error())
Expand Down
Loading

0 comments on commit c9e38da

Please sign in to comment.