Pierre-Olivier Mercier
5f0888a805
All checks were successful
continuous-integration/drone/push Build is passing
492 lines
14 KiB
Go
492 lines
14 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/drone/drone-go/drone"
|
|
"github.com/gin-gonic/gin"
|
|
"golang.org/x/oauth2"
|
|
)
|
|
|
|
var (
|
|
droneToken = ""
|
|
droneConfig *http.Client
|
|
droneEndpoint string
|
|
)
|
|
|
|
func init() {
|
|
flag.StringVar(&droneToken, "drone-token", droneToken, "Token for Drone Oauth")
|
|
flag.StringVar(&droneEndpoint, "drone-endpoint", droneEndpoint, "Drone Endpoint")
|
|
}
|
|
|
|
func initializeDroneOauth() {
|
|
if droneToken != "" {
|
|
config := new(oauth2.Config)
|
|
droneConfig = config.Client(
|
|
oauth2.NoContext,
|
|
&oauth2.Token{
|
|
AccessToken: droneToken,
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
func declareAPIAuthRepositoriesRoutes(router *gin.RouterGroup) {
|
|
router.GET("/repositories", func(c *gin.Context) {
|
|
var u *User
|
|
if user, ok := c.Get("user"); ok {
|
|
u = user.(*User)
|
|
} else {
|
|
u = c.MustGet("LoggedUser").(*User)
|
|
}
|
|
|
|
repositories, err := u.GetRepositories()
|
|
if err != nil {
|
|
log.Println("Unable to GetRepositories:", err)
|
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve your repositories. Please try again in a few moment."})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, repositories)
|
|
})
|
|
router.POST("/repositories", func(c *gin.Context) {
|
|
var u *User
|
|
if user, ok := c.Get("user"); ok {
|
|
u = user.(*User)
|
|
} else {
|
|
u = c.MustGet("LoggedUser").(*User)
|
|
}
|
|
|
|
var repository Repository
|
|
if err := c.ShouldBindJSON(&repository); err != nil {
|
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
return
|
|
}
|
|
|
|
var w *Work
|
|
if work, ok := c.Get("work"); ok {
|
|
w = work.(*Work)
|
|
} else if repository.IdWork > 0 {
|
|
var err error
|
|
w, err = getWork(int(repository.IdWork))
|
|
if err != nil {
|
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Unable to find the given work identifier."})
|
|
return
|
|
}
|
|
} else {
|
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Unable to find the given work identifier."})
|
|
return
|
|
}
|
|
|
|
k, err := u.NewRepository(w, repository.URI)
|
|
if err != nil {
|
|
log.Println("Unable to NewRepository:", err)
|
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to register your public repository. Please try again in a few moment."})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, k)
|
|
})
|
|
|
|
repositoriesRoutes := router.Group("/repositories/:rid")
|
|
repositoriesRoutes.Use(repositoryHandler)
|
|
|
|
repositoriesRoutes.GET("", func(c *gin.Context) {
|
|
repo := c.MustGet("repository").(*Repository)
|
|
|
|
c.JSON(http.StatusOK, repo)
|
|
})
|
|
repositoriesRoutes.PUT("", func(c *gin.Context) {
|
|
current := c.MustGet("repository").(*Repository)
|
|
|
|
var new Repository
|
|
if err := c.ShouldBindJSON(&new); err != nil {
|
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
return
|
|
}
|
|
|
|
new.Id = current.Id
|
|
|
|
u := c.MustGet("LoggedUser").(*User)
|
|
if new.IdUser != current.IdUser && !u.IsAdmin {
|
|
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Operation not allowed."})
|
|
return
|
|
}
|
|
|
|
if repository, err := new.Update(); err != nil {
|
|
log.Println("Unable to Update repository:", err)
|
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during repository updation: %s", err.Error())})
|
|
return
|
|
} else {
|
|
c.JSON(http.StatusOK, repository)
|
|
}
|
|
})
|
|
repositoriesRoutes.DELETE("", func(c *gin.Context) {
|
|
repository := c.MustGet("repository").(*Repository)
|
|
work, err := getWork(int(repository.IdWork))
|
|
if err != nil {
|
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to find related work."})
|
|
return
|
|
|
|
}
|
|
|
|
now := time.Now()
|
|
|
|
if !work.Shown || work.Corrected || work.StartAvailability.After(now) || work.EndAvailability.Before(now) {
|
|
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "The submission is closed."})
|
|
return
|
|
}
|
|
|
|
if _, err := repository.Delete(); err != nil {
|
|
log.Println("Unable to Delete repository:", err)
|
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during repository deletion: %s", err.Error())})
|
|
return
|
|
} else {
|
|
c.JSON(http.StatusOK, nil)
|
|
}
|
|
})
|
|
|
|
repositoriesRoutes.POST("/trigger", func(c *gin.Context) {
|
|
var u *User
|
|
if user, ok := c.Get("user"); ok {
|
|
u = user.(*User)
|
|
} else {
|
|
u = c.MustGet("LoggedUser").(*User)
|
|
}
|
|
repo := c.MustGet("repository").(*Repository)
|
|
work, err := getWork(int(repo.IdWork))
|
|
if err != nil {
|
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to find related work."})
|
|
return
|
|
|
|
}
|
|
|
|
now := time.Now()
|
|
|
|
if repo.LastCheck != nil && !repo.LastCheck.Before(now.Add(-5*time.Minute)) {
|
|
c.AbortWithStatusJSON(http.StatusPaymentRequired, gin.H{"errmsg": "Please wait between two pulls."})
|
|
return
|
|
}
|
|
|
|
TriggerTagUpdate(c, work, repo, u, nil)
|
|
})
|
|
|
|
repositoriesRoutes.GET("/state", func(c *gin.Context) {
|
|
repo := c.MustGet("repository").(*Repository)
|
|
|
|
tmp := strings.Split(repo.DroneRef, "/")
|
|
if len(tmp) < 3 {
|
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "No build number. Please try pulling your work."})
|
|
return
|
|
}
|
|
|
|
nbuild, err := strconv.Atoi(tmp[2])
|
|
if err != nil {
|
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Bad build number. Please retry pulling your work."})
|
|
return
|
|
}
|
|
|
|
client := drone.NewClient(droneEndpoint, droneConfig)
|
|
result, err := client.Build(tmp[0], tmp[1], nbuild)
|
|
if err != nil {
|
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Unable to find the referenced extraction."})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, result)
|
|
})
|
|
|
|
repositoriesRoutes.GET("/state-logs", func(c *gin.Context) {
|
|
repo := c.MustGet("repository").(*Repository)
|
|
|
|
tmp := strings.Split(repo.DroneRef, "/")
|
|
if len(tmp) < 3 {
|
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "No build number. Please try pulling your work."})
|
|
return
|
|
}
|
|
|
|
nbuild, err := strconv.Atoi(tmp[2])
|
|
if err != nil {
|
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Bad build number. Please retry pulling your work."})
|
|
return
|
|
}
|
|
|
|
client := drone.NewClient(droneEndpoint, droneConfig)
|
|
result, err := client.Logs(tmp[0], tmp[1], nbuild, 1, 2)
|
|
if err != nil {
|
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Unable to retrieve logs."})
|
|
return
|
|
}
|
|
|
|
if len(result) > 7 {
|
|
c.JSON(http.StatusOK, result[7:])
|
|
} else {
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
})
|
|
}
|
|
|
|
type GitLabWebhook struct {
|
|
EventName string `json:"event_name"`
|
|
ObjectKind string `json:"object_kind"`
|
|
Ref string
|
|
Repository GitLabRepository
|
|
}
|
|
|
|
func declareCallbacksRoutes(router *gin.RouterGroup) {
|
|
router.POST("/callbacks/trigger.json", func(c *gin.Context) {
|
|
// Check event type
|
|
if c.Request.Header.Get("X-Gitlab-Event") != "Tag Push Hook" {
|
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "This trigger is limited to Tag Push event. Please edit your trigger."})
|
|
return
|
|
}
|
|
|
|
hook := GitLabWebhook{}
|
|
if err := c.ShouldBindJSON(&hook); err != nil {
|
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Check token
|
|
repos, err := getRepositoriesByURI(hook.Repository.URL)
|
|
if err != nil {
|
|
log.Println("Unable to getRepositoriesByURI:", err.Error())
|
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve repositories."})
|
|
return
|
|
}
|
|
|
|
var repo *Repository
|
|
for _, r := range repos {
|
|
log.Println("Received trigger")
|
|
if len(r.Secret) == 0 || base64.StdEncoding.EncodeToString(r.Secret) == c.Request.Header.Get("X-Gitlab-Token") {
|
|
repo = r
|
|
break
|
|
}
|
|
}
|
|
|
|
if repo == nil {
|
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "There are no repositories matching this URL and secret. Please check the Gitlab Secret and retry pushing your tag."})
|
|
return
|
|
}
|
|
|
|
work, err := getWork(int(repo.IdWork))
|
|
if err != nil {
|
|
log.Println("Unable to getWork:", err.Error())
|
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to find related work."})
|
|
return
|
|
|
|
}
|
|
|
|
user, err := getUser(int(repo.IdUser))
|
|
if err != nil {
|
|
log.Println("Unable to getUser:", err.Error())
|
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to find related user."})
|
|
return
|
|
|
|
}
|
|
|
|
tmp := strings.SplitN(hook.Ref, "/", 3)
|
|
if len(tmp) != 3 {
|
|
TriggerTagUpdate(c, work, repo, user, nil)
|
|
return
|
|
}
|
|
|
|
if !strings.HasPrefix(tmp[2], work.Tag) {
|
|
c.AbortWithStatusJSON(http.StatusOK, gin.H{"errmsg": fmt.Sprintf("Ignore ref %q has it doesn't start with %s. Check submission instructions if this is not expected.", tmp[2], work.Tag)})
|
|
return
|
|
}
|
|
|
|
TriggerTagUpdate(c, work, repo, user, &tmp[2])
|
|
})
|
|
}
|
|
|
|
func repositoryHandler(c *gin.Context) {
|
|
var u *User
|
|
if user, ok := c.Get("user"); ok {
|
|
u = user.(*User)
|
|
} else {
|
|
u = c.MustGet("LoggedUser").(*User)
|
|
}
|
|
|
|
if rid, err := strconv.Atoi(string(c.Param("rid"))); err != nil {
|
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad repository identifier."})
|
|
return
|
|
} else if u.IsAdmin {
|
|
if repository, err := getRepository(rid); err != nil {
|
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Repository not found."})
|
|
return
|
|
} else {
|
|
c.Set("repository", repository)
|
|
c.Next()
|
|
}
|
|
} else if repository, err := u.getRepository(rid); err != nil {
|
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Repository not found."})
|
|
return
|
|
} else {
|
|
c.Set("repository", repository)
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
func TriggerTagUpdate(c *gin.Context, work *Work, repo *Repository, u *User, tag *string) {
|
|
now := time.Now()
|
|
|
|
if !work.Shown || work.Corrected || work.StartAvailability.After(now) || work.EndAvailability.Add(time.Hour).Before(now) {
|
|
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "The submission is closed."})
|
|
return
|
|
}
|
|
|
|
repo_tag := work.Tag
|
|
if tag != nil {
|
|
repo_tag = *tag
|
|
}
|
|
|
|
var pgp_keys string
|
|
keys, err := u.GetKeys()
|
|
if err != nil {
|
|
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Something goes really wrong with your PGP keys: unable to retrieve them."})
|
|
return
|
|
}
|
|
|
|
for _, k := range keys {
|
|
pgp_keys += k.Content + "\n"
|
|
}
|
|
|
|
client := drone.NewClient(droneEndpoint, droneConfig)
|
|
result, err := client.BuildCreate("srs", "atsebay.t-worker", "", "master", map[string]string{
|
|
"REPO_URL": repo.URI,
|
|
"REPO_TAG": repo_tag,
|
|
"USER_PGP_PUBKEY": pgp_keys,
|
|
"LOGIN": u.Login,
|
|
"DEST": fmt.Sprintf("%d", work.Id),
|
|
})
|
|
if err != nil {
|
|
log.Println("Unable to communicate with Drone:", err.Error())
|
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to communication with the extraction service."})
|
|
return
|
|
}
|
|
|
|
repo.DroneRef = fmt.Sprintf("%s/%s/%d", "srs", "atsebay.t-worker", result.Number)
|
|
repo.LastCheck = &now
|
|
repo.Update()
|
|
|
|
repo.Secret = []byte{}
|
|
c.JSON(http.StatusOK, repo)
|
|
}
|
|
|
|
type Repository struct {
|
|
Id int64 `json:"id"`
|
|
IdUser int64 `json:"id_user"`
|
|
IdWork int64 `json:"id_work"`
|
|
URI string `json:"uri"`
|
|
Secret []byte `json:"secret,omitempty"`
|
|
LastCheck *time.Time `json:"last_check"`
|
|
DroneRef string `json:"drone_ref,omitempty"`
|
|
}
|
|
|
|
func (u *User) GetRepositories() (repositories []*Repository, err error) {
|
|
if rows, errr := DBQuery("SELECT id_repository, id_user, id_work, uri, secret, last_check, droneref FROM user_work_repositories WHERE id_user=?", u.Id); errr != nil {
|
|
return nil, errr
|
|
} else {
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
var repo Repository
|
|
if err = rows.Scan(&repo.Id, &repo.IdUser, &repo.IdWork, &repo.URI, &repo.Secret, &repo.LastCheck, &repo.DroneRef); err != nil {
|
|
return
|
|
}
|
|
repositories = append(repositories, &repo)
|
|
}
|
|
if err = rows.Err(); err != nil {
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
func getRepositoriesByURI(uri string) (repositories []*Repository, err error) {
|
|
if rows, errr := DBQuery("SELECT id_repository, id_user, id_work, uri, secret, last_check, droneref FROM user_work_repositories WHERE uri=?", uri); errr != nil {
|
|
return nil, errr
|
|
} else {
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
var repo Repository
|
|
if err = rows.Scan(&repo.Id, &repo.IdUser, &repo.IdWork, &repo.URI, &repo.Secret, &repo.LastCheck, &repo.DroneRef); err != nil {
|
|
return
|
|
}
|
|
repositories = append(repositories, &repo)
|
|
}
|
|
if err = rows.Err(); err != nil {
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
func getRepository(id int) (r *Repository, err error) {
|
|
r = new(Repository)
|
|
err = DBQueryRow("SELECT id_repository, id_user, id_work, uri, secret, last_check, droneref FROM user_work_repositories WHERE id_repository=?", id).Scan(&r.Id, &r.IdUser, &r.IdWork, &r.URI, &r.Secret, &r.LastCheck, &r.DroneRef)
|
|
return
|
|
}
|
|
|
|
func (u *User) getRepository(id int) (r *Repository, err error) {
|
|
r = new(Repository)
|
|
err = DBQueryRow("SELECT id_repository, id_user, id_work, uri, secret, last_check, droneref FROM user_work_repositories WHERE id_repository=? AND id_user=?", id, u.Id).Scan(&r.Id, &r.IdUser, &r.IdWork, &r.URI, &r.Secret, &r.LastCheck, &r.DroneRef)
|
|
return
|
|
}
|
|
|
|
func (u *User) NewRepository(w *Work, uri string) (*Repository, error) {
|
|
secret := make([]byte, 24)
|
|
_, err := rand.Read(secret)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if res, err := DBExec("INSERT INTO user_work_repositories (id_user, id_work, uri, secret, droneref) VALUES (?, ?, ?, ?, ?)", u.Id, w.Id, uri, secret, ""); err != nil {
|
|
return nil, err
|
|
} else if rid, err := res.LastInsertId(); err != nil {
|
|
return nil, err
|
|
} else {
|
|
return &Repository{rid, u.Id, w.Id, uri, secret, nil, ""}, nil
|
|
}
|
|
}
|
|
|
|
func (r *Repository) Update() (*Repository, error) {
|
|
if _, err := DBExec("UPDATE user_work_repositories SET id_user = ?, id_work = ?, uri = ?, secret = ?, last_check = ?, droneref = ? WHERE id_repository = ?", r.IdUser, r.IdWork, r.URI, r.Secret, r.LastCheck, r.DroneRef, r.Id); err != nil {
|
|
return nil, err
|
|
} else {
|
|
return r, err
|
|
}
|
|
}
|
|
|
|
func (r Repository) Delete() (int64, error) {
|
|
if res, err := DBExec("DELETE FROM user_work_repositories WHERE id_repository = ?", r.Id); err != nil {
|
|
return 0, err
|
|
} else if nb, err := res.RowsAffected(); err != nil {
|
|
return 0, err
|
|
} else {
|
|
return nb, err
|
|
}
|
|
}
|
|
|
|
func ClearRepositories() (int64, error) {
|
|
if res, err := DBExec("DELETE FROM user_work_repositories"); err != nil {
|
|
return 0, err
|
|
} else if nb, err := res.RowsAffected(); err != nil {
|
|
return 0, err
|
|
} else {
|
|
return nb, err
|
|
}
|
|
}
|