Working webhook
This commit is contained in:
parent
c976f7b53f
commit
7b45214ca9
1
db.go
1
db.go
@ -234,6 +234,7 @@ CREATE TABLE IF NOT EXISTS user_work_repositories(
|
|||||||
id_user INTEGER NOT NULL,
|
id_user INTEGER NOT NULL,
|
||||||
id_work INTEGER NOT NULL,
|
id_work INTEGER NOT NULL,
|
||||||
uri VARCHAR(255) NOT NULL,
|
uri VARCHAR(255) NOT NULL,
|
||||||
|
secret BLOB NOT NULL,
|
||||||
last_check TIMESTAMP NULL DEFAULT NULL,
|
last_check TIMESTAMP NULL DEFAULT NULL,
|
||||||
droneref VARCHAR(255) NOT NULL,
|
droneref VARCHAR(255) NOT NULL,
|
||||||
FOREIGN KEY(id_user) REFERENCES users(id_user),
|
FOREIGN KEY(id_user) REFERENCES users(id_user),
|
||||||
|
@ -171,6 +171,7 @@ type GitLabRepository struct {
|
|||||||
Path string
|
Path string
|
||||||
PathWithNamespace string `json:"path_with_namespace"`
|
PathWithNamespace string `json:"path_with_namespace"`
|
||||||
DefaultBranch string `json:"default_branch"`
|
DefaultBranch string `json:"default_branch"`
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
SshUrlToRepo string `json:"ssh_url_to_repo"`
|
SshUrlToRepo string `json:"ssh_url_to_repo"`
|
||||||
HttpUrlToRepo string `json:"http_url_to_repo"`
|
HttpUrlToRepo string `json:"http_url_to_repo"`
|
||||||
AvatarURL string `json:"avatar_url"`
|
AvatarURL string `json:"avatar_url"`
|
||||||
|
171
repositories.go
171
repositories.go
@ -1,6 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@ -169,34 +171,12 @@ func declareAPIAuthRepositoriesRoutes(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
now := time.Now()
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
if repo.LastCheck != nil && !repo.LastCheck.Before(now.Add(-5*time.Minute)) {
|
if repo.LastCheck != nil && !repo.LastCheck.Before(now.Add(-5*time.Minute)) {
|
||||||
c.AbortWithStatusJSON(http.StatusPaymentRequired, gin.H{"errmsg": "Please wait between two pulls."})
|
c.AbortWithStatusJSON(http.StatusPaymentRequired, gin.H{"errmsg": "Please wait between two pulls."})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
client := drone.NewClient(droneEndpoint, droneConfig)
|
TriggerTagUpdate(c, work, repo, u, nil)
|
||||||
result, err := client.BuildCreate("srs", "atsebay.t-worker", "", "master", map[string]string{
|
|
||||||
"REPO_URL": repo.URI,
|
|
||||||
"REPO_TAG": work.Tag,
|
|
||||||
"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()
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, repo)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
repositoriesRoutes.GET("/state", func(c *gin.Context) {
|
repositoriesRoutes.GET("/state", func(c *gin.Context) {
|
||||||
@ -254,20 +234,77 @@ func declareAPIAuthRepositoriesRoutes(router *gin.RouterGroup) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GitLabWebhook struct {
|
||||||
|
EventName string `json:"event_name"`
|
||||||
|
ObjectKind string `json:"object_kind"`
|
||||||
|
Ref string
|
||||||
|
Repository GitLabRepository
|
||||||
|
}
|
||||||
|
|
||||||
func declareCallbacksRoutes(router *gin.RouterGroup) {
|
func declareCallbacksRoutes(router *gin.RouterGroup) {
|
||||||
router.POST("/callbacks/trigger.json", func(c *gin.Context) {
|
router.POST("/callbacks/trigger.json", func(c *gin.Context) {
|
||||||
log.Println("Received trigger")
|
// Check event type
|
||||||
log.Println("X-Gitlab-Token", c.Request.Header.Get("X-Gitlab-Token"))
|
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
|
||||||
|
}
|
||||||
|
|
||||||
tmp := map[string]interface{}{}
|
hook := GitLabWebhook{}
|
||||||
if err := c.ShouldBindJSON(&tmp); err != nil {
|
if err := c.ShouldBindJSON(&hook); err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Content", tmp)
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
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])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,24 +336,80 @@ func repositoryHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
"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 {
|
type Repository struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
IdUser int64 `json:"id_user"`
|
IdUser int64 `json:"id_user"`
|
||||||
IdWork int64 `json:"id_work"`
|
IdWork int64 `json:"id_work"`
|
||||||
URI string `json:"uri"`
|
URI string `json:"uri"`
|
||||||
|
Secret []byte `json:"secret,omitempty"`
|
||||||
LastCheck *time.Time `json:"last_check"`
|
LastCheck *time.Time `json:"last_check"`
|
||||||
DroneRef string `json:"drone_ref,omitempty"`
|
DroneRef string `json:"drone_ref,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) GetRepositories() (repositories []*Repository, err error) {
|
func (u *User) GetRepositories() (repositories []*Repository, err error) {
|
||||||
if rows, errr := DBQuery("SELECT id_repository, id_user, id_work, uri, last_check, droneref FROM user_work_repositories WHERE id_user=?", u.Id); errr != nil {
|
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
|
return nil, errr
|
||||||
} else {
|
} else {
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var repo Repository
|
var repo Repository
|
||||||
if err = rows.Scan(&repo.Id, &repo.IdUser, &repo.IdWork, &repo.URI, &repo.LastCheck, &repo.DroneRef); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
repositories = append(repositories, &repo)
|
repositories = append(repositories, &repo)
|
||||||
@ -331,28 +424,34 @@ func (u *User) GetRepositories() (repositories []*Repository, err error) {
|
|||||||
|
|
||||||
func getRepository(id int) (r *Repository, err error) {
|
func getRepository(id int) (r *Repository, err error) {
|
||||||
r = new(Repository)
|
r = new(Repository)
|
||||||
err = DBQueryRow("SELECT id_repository, id_user, id_work, uri, last_check, droneref FROM user_work_repositories WHERE id_repository=?", id).Scan(&r.Id, &r.IdUser, &r.IdWork, &r.URI, &r.LastCheck, &r.DroneRef)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) getRepository(id int) (r *Repository, err error) {
|
func (u *User) getRepository(id int) (r *Repository, err error) {
|
||||||
r = new(Repository)
|
r = new(Repository)
|
||||||
err = DBQueryRow("SELECT id_repository, id_user, id_work, uri, 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.LastCheck, &r.DroneRef)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) NewRepository(w *Work, uri string) (*Repository, error) {
|
func (u *User) NewRepository(w *Work, uri string) (*Repository, error) {
|
||||||
if res, err := DBExec("INSERT INTO user_work_repositories (id_user, id_work, uri, droneref) VALUES (?, ?, ?, ?)", u.Id, w.Id, uri, ""); err != nil {
|
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
|
return nil, err
|
||||||
} else if rid, err := res.LastInsertId(); err != nil {
|
} else if rid, err := res.LastInsertId(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return &Repository{rid, u.Id, w.Id, uri, nil, ""}, nil
|
return &Repository{rid, u.Id, w.Id, uri, secret, nil, ""}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Repository) Update() (*Repository, error) {
|
func (r *Repository) Update() (*Repository, error) {
|
||||||
if _, err := DBExec("UPDATE user_work_repositories SET id_user = ?, id_work = ?, uri = ?, last_check = ?, droneref = ? WHERE id_repository = ?", r.IdUser, r.IdWork, r.URI, r.LastCheck, r.DroneRef, r.Id); err != nil {
|
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
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return r, err
|
return r, err
|
||||||
|
@ -84,7 +84,23 @@
|
|||||||
<div class="card-body d-flex justify-content-between">
|
<div class="card-body d-flex justify-content-between">
|
||||||
<div class="d-flex flex-column justify-content-center">
|
<div class="d-flex flex-column justify-content-center">
|
||||||
<div>
|
<div>
|
||||||
Dépôt lié : <strong style="font-family: monospace">{repo.uri}</strong><br>
|
<div class="row">
|
||||||
|
<label for={repo.id + "url"} class="col-sm-6 col-form-label">Dépôt lié :</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input type="text" class="form-control form-control-sm" style="font-family: monospace" disabled id={repo.id + "url"} value={repo.uri}>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label for={repo.id + "secret"} class="col-sm-6 col-form-label">Webhook Secret token (à indiquer dans GitLab) :</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type={repo.show_secret?"text":"password"} class="form-control form-control-sm" disabled id={repo.id + "secret"} value={repo.secret}>
|
||||||
|
<button class="btn btn-sm btn-outline-info" on:click={() => { repo.show_secret = !repo.show_secret}}>
|
||||||
|
<i class="bi" class:bi-eye={!repo.show_secret} class:bi-eye-slash={repo.show_secret}></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Dernière récupération : <strong>{#if repo.last_check}<DateFormat date={new Date(repo.last_check)} dateStyle="medium" timeStyle="medium" />{:else}-{/if}</strong>
|
Dernière récupération : <strong>{#if repo.last_check}<DateFormat date={new Date(repo.last_check)} dateStyle="medium" timeStyle="medium" />{:else}-{/if}</strong>
|
||||||
{#if repo_pull_state}
|
{#if repo_pull_state}
|
||||||
{#await repo_pull_state}
|
{#await repo_pull_state}
|
||||||
|
@ -5,11 +5,12 @@ export class WorkRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update({ id, id_user, id_work, uri, last_check }) {
|
update({ id, id_user, id_work, uri, secret, last_check }) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.id_user = id_user;
|
this.id_user = id_user;
|
||||||
this.id_work = id_work;
|
this.id_work = id_work;
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
|
this.secret = secret;
|
||||||
this.last_check = last_check;
|
this.last_check = last_check;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,7 +146,7 @@
|
|||||||
<li>être dans l'espace de nom de votre utilisateur (à la fin de la liste des <span class="fst-italic">namespaces</span>),</li>
|
<li>être dans l'espace de nom de votre utilisateur (à la fin de la liste des <span class="fst-italic">namespaces</span>),</li>
|
||||||
<li>avoir la visibilité « Privé »,</li>
|
<li>avoir la visibilité « Privé »,</li>
|
||||||
<li>avoir invité <a href="https://gitlab.cri.epita.fr/nemunaire" target="_blank" style="font-family: monospace">nemunaire</a> avec le rôle <span class="fst-italic">Reporter</span> une fois le dépôt créé,</li>
|
<li>avoir invité <a href="https://gitlab.cri.epita.fr/nemunaire" target="_blank" style="font-family: monospace">nemunaire</a> avec le rôle <span class="fst-italic">Reporter</span> une fois le dépôt créé,</li>
|
||||||
<li>configuré un <span class="fst-italic">webhook</span> pointant sur <code>https://lessons.nemunai.re/api/callbacks/trigger.json</code></li>
|
<li>avoir configuré un <span class="fst-italic">webhook <strong>Tag push events</strong></span> pointant sur <code>https://lessons.nemunai.re/api/callbacks/trigger.json</code> avec le secret donné.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{#if w.tag}
|
{#if w.tag}
|
||||||
|
Reference in New Issue
Block a user