166 lines
4.1 KiB
Go
166 lines
4.1 KiB
Go
package github
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
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"
|
|
)
|
|
|
|
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
|
|
var response *gh.Response
|
|
var options *gh.ListMembersOptions
|
|
if install.GetAccount().GetType() == "Organization" {
|
|
var temp_members []*gh.User
|
|
|
|
insClient := client.GetInstallationClient(install.GetID())
|
|
org := install.GetAccount().GetLogin()
|
|
temp_members, response, err = insClient.Organizations.ListMembers(ctx, org, options)
|
|
members = append(members, temp_members...)
|
|
|
|
for response.NextPage != 0 {
|
|
options := &gh.ListMembersOptions{
|
|
ListOptions: gh.ListOptions{
|
|
Page: response.NextPage,
|
|
},
|
|
}
|
|
temp_members, response, err = insClient.Organizations.ListMembers(ctx, org, options)
|
|
members = append(members, temp_members...)
|
|
}
|
|
|
|
} 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
|
|
}
|