init from gitlab
This commit is contained in:
86
internal/server/github/app.go
Normal file
86
internal/server/github/app.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/bradleyfalzon/ghinstallation/v2"
|
||||
gh "github.com/google/go-github/v48/github"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"gitlab.com/texm/shokku/internal/env"
|
||||
"gitlab.com/texm/shokku/internal/models"
|
||||
)
|
||||
|
||||
type AppClient struct {
|
||||
*gh.Client
|
||||
appsTransport *ghinstallation.AppsTransport
|
||||
}
|
||||
|
||||
func GetAppClient(e *env.Env) (*AppClient, error) {
|
||||
var dbApp models.GithubApp
|
||||
if err := e.DB.Find(&dbApp).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transport, err := ghinstallation.NewAppsTransport(
|
||||
http.DefaultTransport, dbApp.AppId, []byte(dbApp.PEM))
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("failed to create transport")
|
||||
return nil, err
|
||||
}
|
||||
appClient := &AppClient{
|
||||
Client: gh.NewClient(&http.Client{Transport: transport}),
|
||||
appsTransport: transport,
|
||||
}
|
||||
return appClient, nil
|
||||
}
|
||||
|
||||
type AppInstallationClient struct {
|
||||
*gh.Client
|
||||
}
|
||||
|
||||
func (c *AppClient) GetInstallationClient(id int64) *AppInstallationClient {
|
||||
transport := ghinstallation.NewFromAppsTransport(c.appsTransport, id)
|
||||
client := gh.NewClient(&http.Client{Transport: transport})
|
||||
return &AppInstallationClient{Client: client}
|
||||
}
|
||||
|
||||
func CompleteAppManifest(e *env.Env, code string) (*gh.AppConfig, error) {
|
||||
ctx := context.Background()
|
||||
client := gh.NewClient(nil)
|
||||
cfg, _, ghErr := client.Apps.CompleteAppManifest(ctx, code)
|
||||
if ghErr != nil {
|
||||
return nil, ghErr
|
||||
}
|
||||
|
||||
appId := cfg.GetID()
|
||||
dbApp := models.GithubApp{AppId: appId}
|
||||
if dbErr := e.DB.FirstOrCreate(&dbApp).Error; dbErr != nil {
|
||||
log.Error().Err(dbErr).Msg("failed db lookup")
|
||||
return nil, dbErr
|
||||
}
|
||||
|
||||
dbApp.AppId = appId
|
||||
dbApp.ClientId = cfg.GetClientID()
|
||||
dbApp.NodeId = cfg.GetNodeID()
|
||||
dbApp.Slug = cfg.GetSlug()
|
||||
dbApp.PEM = cfg.GetPEM()
|
||||
dbApp.ClientSecret = cfg.GetClientSecret()
|
||||
dbApp.WebhookSecret = cfg.GetWebhookSecret()
|
||||
|
||||
// saveRes := e.DB.Where(&models.GithubApp{AppId: appId}).Save(&dbApp)
|
||||
if err := e.DB.Save(&dbApp).Error; err != nil {
|
||||
log.Error().Err(err).Msg("failed to save db app")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (c *AppClient) GetApp(ctx context.Context) (*gh.App, error) {
|
||||
app, _, err := c.Apps.Get(ctx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return app, nil
|
||||
}
|
||||
148
internal/server/github/sync.go
Normal file
148
internal/server/github/sync.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
gh "github.com/google/go-github/v48/github"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gitlab.com/texm/shokku/internal/env"
|
||||
"gitlab.com/texm/shokku/internal/models"
|
||||
"gorm.io/gorm/clause"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type githubUser struct {
|
||||
User models.User
|
||||
SSHKeys []models.SSHKey
|
||||
}
|
||||
|
||||
func Sync(e *env.Env) error {
|
||||
if err := SyncUsersToDB(e); err != nil {
|
||||
return fmt.Errorf("failed to sync github users: %w", err)
|
||||
}
|
||||
|
||||
//if err := SyncInstallationStatus(e); err != nil {
|
||||
// return fmt.Errorf()
|
||||
//}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SyncUsersToDB asynchronously get users in organization & add to db
|
||||
func SyncUsersToDB(e *env.Env) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
defer cancel()
|
||||
|
||||
client, clientErr := GetAppClient(e)
|
||||
if clientErr != nil {
|
||||
return fmt.Errorf("failed to get app client: %w", clientErr)
|
||||
}
|
||||
|
||||
installs, _, installsErr := client.Apps.ListInstallations(ctx, nil)
|
||||
if installsErr != nil {
|
||||
return installsErr
|
||||
}
|
||||
|
||||
var users []githubUser
|
||||
for _, install := range installs {
|
||||
var members []*gh.User
|
||||
var err error
|
||||
if install.GetAccount().GetType() == "Organization" {
|
||||
insClient := client.GetInstallationClient(install.GetID())
|
||||
org := install.GetAccount().GetLogin()
|
||||
members, _, err = insClient.Organizations.ListMembers(ctx, org, nil)
|
||||
} else {
|
||||
members = append(members, install.GetAccount())
|
||||
}
|
||||
if err != nil {
|
||||
log.Error().Err(err).
|
||||
Int64("installation_id", install.GetID()).
|
||||
Msg("failed to get members")
|
||||
continue
|
||||
}
|
||||
for _, member := range members {
|
||||
users = append(users, fetchUserInfo(member))
|
||||
}
|
||||
}
|
||||
|
||||
conflict := clause.OnConflict{
|
||||
DoUpdates: clause.AssignmentColumns([]string{"updated_at"}),
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
if err := e.DB.Clauses(conflict).Create(&u.User).Error; err != nil {
|
||||
log.Error().Err(err).
|
||||
Str("name", u.User.Name).
|
||||
Msg("failed to create user")
|
||||
continue
|
||||
}
|
||||
for _, key := range u.SSHKeys {
|
||||
key.UserID = u.User.ID
|
||||
if err := e.DB.Clauses(conflict).Create(&key).Error; err != nil {
|
||||
log.Error().Err(err).
|
||||
Str("name", u.User.Name).
|
||||
Msg("failed to create user ssh key")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
oneMinuteAgo := time.Now().Add(-time.Minute)
|
||||
var deletedUsers []models.User
|
||||
rUsers := e.DB.Where("updated_at < ?", oneMinuteAgo).Delete(&deletedUsers)
|
||||
if rUsers.Error != nil {
|
||||
log.Error().Err(rUsers.Error).Msg("failed to delete old users")
|
||||
}
|
||||
|
||||
var deletedKeys []models.SSHKey
|
||||
rKeys := e.DB.Where("updated_at < ?", oneMinuteAgo).Delete(&deletedKeys)
|
||||
if rKeys.Error != nil {
|
||||
log.Error().Err(rKeys.Error).Msg("failed to delete old ssh keys")
|
||||
}
|
||||
|
||||
log.Debug().
|
||||
Int("num_installations", len(installs)).
|
||||
Int("synced_users", len(users)).
|
||||
Int64("removed_users", rUsers.RowsAffected).
|
||||
Int64("removed_keys", rKeys.RowsAffected).
|
||||
Msgf("github user sync complete")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fetchUserInfo(u *gh.User) githubUser {
|
||||
username := u.GetLogin()
|
||||
user := githubUser{
|
||||
User: models.User{Name: username, Source: "github"},
|
||||
}
|
||||
userKeysApi := fmt.Sprintf("https://api.github.com/users/%s/keys", username)
|
||||
res, reqErr := http.Get(userKeysApi)
|
||||
if reqErr != nil {
|
||||
log.Error().Err(reqErr).
|
||||
Str("username", username).
|
||||
Msg("failed to get users SSH keys")
|
||||
return user
|
||||
}
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to read response body")
|
||||
return user
|
||||
}
|
||||
|
||||
var keys []gh.Key
|
||||
if err := json.Unmarshal(body, &keys); err != nil {
|
||||
log.Error().Err(err).Msg("failed to unmarshal keys")
|
||||
return user
|
||||
}
|
||||
|
||||
user.SSHKeys = make([]models.SSHKey, len(keys))
|
||||
for i, key := range keys {
|
||||
user.SSHKeys[i] = models.SSHKey{
|
||||
GithubID: key.GetID(),
|
||||
Key: key.GetKey(),
|
||||
}
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
56
internal/server/github/user.go
Normal file
56
internal/server/github/user.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
gh "github.com/google/go-github/v48/github"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gitlab.com/texm/shokku/internal/env"
|
||||
"gitlab.com/texm/shokku/internal/models"
|
||||
"golang.org/x/oauth2"
|
||||
ghOAuth "golang.org/x/oauth2/github"
|
||||
)
|
||||
|
||||
type UserClient struct {
|
||||
client *gh.Client
|
||||
}
|
||||
|
||||
type CodeExchangeParams struct {
|
||||
Code string
|
||||
Scopes []string
|
||||
RedirectURL string
|
||||
}
|
||||
|
||||
func ExchangeCode(ctx context.Context, e *env.Env, p CodeExchangeParams) (*UserClient, error) {
|
||||
var ghApp models.GithubApp
|
||||
if err := e.DB.WithContext(ctx).First(&ghApp).Error; err != nil {
|
||||
log.Error().Err(err).Msg("no github app in db")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf := &oauth2.Config{
|
||||
ClientID: ghApp.ClientId,
|
||||
ClientSecret: ghApp.ClientSecret,
|
||||
Scopes: p.Scopes,
|
||||
RedirectURL: p.RedirectURL,
|
||||
Endpoint: ghOAuth.Endpoint,
|
||||
}
|
||||
token, err := conf.Exchange(ctx, p.Code)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to exchange code for token")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tokenSource := oauth2.StaticTokenSource(token)
|
||||
oauthClient := oauth2.NewClient(ctx, tokenSource)
|
||||
client := gh.NewClient(oauthClient)
|
||||
return &UserClient{client}, nil
|
||||
}
|
||||
|
||||
func (u *UserClient) GetUser(ctx context.Context) (*gh.User, error) {
|
||||
user, _, err := u.client.Users.Get(ctx, "")
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to get user")
|
||||
return nil, err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
Reference in New Issue
Block a user