| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427 | // Copyright 2014 The Gogs Authors. All rights reserved.// Use of this source code is governed by a MIT-style// license that can be found in the LICENSE file.package modelsimport (	"crypto/tls"	"encoding/json"	"errors"	"io/ioutil"	"sync"	"time"	"github.com/gogits/gogs/modules/httplib"	"github.com/gogits/gogs/modules/log"	"github.com/gogits/gogs/modules/setting"	"github.com/gogits/gogs/modules/uuid")var (	ErrWebhookNotExist = errors.New("Webhook does not exist"))type HookContentType intconst (	JSON HookContentType = iota + 1	FORM)var hookContentTypes = map[string]HookContentType{	"json": JSON,	"form": FORM,}// ToHookContentType returns HookContentType by given name.func ToHookContentType(name string) HookContentType {	return hookContentTypes[name]}func (t HookContentType) Name() string {	switch t {	case JSON:		return "json"	case FORM:		return "form"	}	return ""}// IsValidHookContentType returns true if given name is a valid hook content type.func IsValidHookContentType(name string) bool {	_, ok := hookContentTypes[name]	return ok}// HookEvent represents events that will delivery hook.type HookEvent struct {	PushOnly bool `json:"push_only"`}// Webhook represents a web hook object.type Webhook struct {	Id           int64	RepoId       int64	Url          string `xorm:"TEXT"`	ContentType  HookContentType	Secret       string `xorm:"TEXT"`	Events       string `xorm:"TEXT"`	*HookEvent   `xorm:"-"`	IsSsl        bool	IsActive     bool	HookTaskType HookTaskType	Meta         string `xorm:"TEXT"` // store hook-specific attributes	OrgId        int64	Created      time.Time `xorm:"CREATED"`	Updated      time.Time `xorm:"UPDATED"`}// GetEvent handles conversion from Events to HookEvent.func (w *Webhook) GetEvent() {	w.HookEvent = &HookEvent{}	if err := json.Unmarshal([]byte(w.Events), w.HookEvent); err != nil {		log.Error(4, "webhook.GetEvent(%d): %v", w.Id, err)	}}func (w *Webhook) GetSlackHook() *Slack {	s := &Slack{}	if err := json.Unmarshal([]byte(w.Meta), s); err != nil {		log.Error(4, "webhook.GetSlackHook(%d): %v", w.Id, err)	}	return s}// UpdateEvent handles conversion from HookEvent to Events.func (w *Webhook) UpdateEvent() error {	data, err := json.Marshal(w.HookEvent)	w.Events = string(data)	return err}// HasPushEvent returns true if hook enabled push event.func (w *Webhook) HasPushEvent() bool {	if w.PushOnly {		return true	}	return false}// CreateWebhook creates a new web hook.func CreateWebhook(w *Webhook) error {	_, err := x.Insert(w)	return err}// GetWebhookById returns webhook by given ID.func GetWebhookById(hookId int64) (*Webhook, error) {	w := &Webhook{Id: hookId}	has, err := x.Get(w)	if err != nil {		return nil, err	} else if !has {		return nil, ErrWebhookNotExist	}	return w, nil}// GetActiveWebhooksByRepoId returns all active webhooks of repository.func GetActiveWebhooksByRepoId(repoId int64) (ws []*Webhook, err error) {	err = x.Where("repo_id=?", repoId).And("is_active=?", true).Find(&ws)	return ws, err}// GetWebhooksByRepoId returns all webhooks of repository.func GetWebhooksByRepoId(repoId int64) (ws []*Webhook, err error) {	err = x.Find(&ws, &Webhook{RepoId: repoId})	return ws, err}// UpdateWebhook updates information of webhook.func UpdateWebhook(w *Webhook) error {	_, err := x.Id(w.Id).AllCols().Update(w)	return err}// DeleteWebhook deletes webhook of repository.func DeleteWebhook(hookId int64) error {	_, err := x.Delete(&Webhook{Id: hookId})	return err}// GetWebhooksByOrgId returns all webhooks for an organization.func GetWebhooksByOrgId(orgId int64) (ws []*Webhook, err error) {	err = x.Find(&ws, &Webhook{OrgId: orgId})	return ws, err}// GetActiveWebhooksByOrgId returns all active webhooks for an organization.func GetActiveWebhooksByOrgId(orgId int64) (ws []*Webhook, err error) {	err = x.Where("org_id=?", orgId).And("is_active=?", true).Find(&ws)	return ws, err}//   ___ ___                __   ___________              __//  /   |   \  ____   ____ |  | _\__    ___/____    _____|  | __// /    ~    \/  _ \ /  _ \|  |/ / |    |  \__  \  /  ___/  |/ /// \    Y    (  <_> |  <_> )    <  |    |   / __ \_\___ \|    <//  \___|_  / \____/ \____/|__|_ \ |____|  (____  /____  >__|_ \//        \/                    \/              \/     \/     \/type HookTaskType intconst (	GOGS HookTaskType = iota + 1	SLACK)var hookTaskTypes = map[string]HookTaskType{	"gogs":  GOGS,	"slack": SLACK,}// ToHookTaskType returns HookTaskType by given name.func ToHookTaskType(name string) HookTaskType {	return hookTaskTypes[name]}func (t HookTaskType) Name() string {	switch t {	case GOGS:		return "gogs"	case SLACK:		return "slack"	}	return ""}// IsValidHookTaskType returns true if given name is a valid hook task type.func IsValidHookTaskType(name string) bool {	_, ok := hookTaskTypes[name]	return ok}type HookEventType stringconst (	HOOK_EVENT_PUSH HookEventType = "push")// FIXME: just use go-gogs-client structs maybe?type PayloadAuthor struct {	Name     string `json:"name"`	Email    string `json:"email"`	UserName string `json:"username"`}type PayloadCommit struct {	Id      string         `json:"id"`	Message string         `json:"message"`	Url     string         `json:"url"`	Author  *PayloadAuthor `json:"author"`}type PayloadRepo struct {	Id          int64          `json:"id"`	Name        string         `json:"name"`	Url         string         `json:"url"`	Description string         `json:"description"`	Website     string         `json:"website"`	Watchers    int            `json:"watchers"`	Owner       *PayloadAuthor `json:"owner"`	Private     bool           `json:"private"`}type BasePayload interface {	GetJSONPayload() ([]byte, error)}// Payload represents a payload information of hook.type Payload struct {	Secret     string           `json:"secret"`	Ref        string           `json:"ref"`	Commits    []*PayloadCommit `json:"commits"`	Repo       *PayloadRepo     `json:"repository"`	Pusher     *PayloadAuthor   `json:"pusher"`	Before     string           `json:"before"`	After      string           `json:"after"`	CompareUrl string           `json:"compare_url"`}func (p Payload) GetJSONPayload() ([]byte, error) {	data, err := json.Marshal(p)	if err != nil {		return []byte{}, err	}	return data, nil}// HookTask represents a hook task.type HookTask struct {	ID             int64 `xorm:"pk autoincr"`	RepoID         int64 `xorm:"INDEX"`	HookID         int64	Uuid           string	Type           HookTaskType	Url            string	BasePayload    `xorm:"-"`	PayloadContent string `xorm:"TEXT"`	ContentType    HookContentType	EventType      HookEventType	IsSsl          bool	IsDelivered    bool	Delivered      int64	IsSucceed      bool}// CreateHookTask creates a new hook task,// it handles conversion from Payload to PayloadContent.func CreateHookTask(t *HookTask) error {	data, err := t.BasePayload.GetJSONPayload()	if err != nil {		return err	}	t.Uuid = uuid.NewV4().String()	t.PayloadContent = string(data)	_, err = x.Insert(t)	return err}// UpdateHookTask updates information of hook task.func UpdateHookTask(t *HookTask) error {	_, err := x.Id(t.ID).AllCols().Update(t)	return err}type hookQueue struct {	// Make sure one repository only occur once in the queue.	lock    sync.Mutex	repoIDs map[int64]bool	queue chan int64}func (q *hookQueue) removeRepoID(id int64) {	q.lock.Lock()	defer q.lock.Unlock()	delete(q.repoIDs, id)}func (q *hookQueue) addRepoID(id int64) {	q.lock.Lock()	if q.repoIDs[id] {		q.lock.Unlock()		return	}	q.repoIDs[id] = true	q.lock.Unlock()	q.queue <- id}// AddRepoID adds repository ID to hook delivery queue.func (q *hookQueue) AddRepoID(id int64) {	go q.addRepoID(id)}var HookQueue *hookQueuefunc deliverHook(t *HookTask) {	timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second	req := httplib.Post(t.Url).SetTimeout(timeout, timeout).		Header("X-Gogs-Delivery", t.Uuid).		Header("X-Gogs-Event", string(t.EventType)).		SetTLSClientConfig(&tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify})	switch t.ContentType {	case JSON:		req = req.Header("Content-Type", "application/json").Body(t.PayloadContent)	case FORM:		req.Param("payload", t.PayloadContent)	}	t.IsDelivered = true	// FIXME: record response.	switch t.Type {	case GOGS:		{			if resp, err := req.Response(); err != nil {				log.Error(5, "Delivery: %v", err)			} else {				resp.Body.Close()				t.IsSucceed = true			}		}	case SLACK:		{			if resp, err := req.Response(); err != nil {				log.Error(5, "Delivery: %v", err)			} else {				defer resp.Body.Close()				contents, err := ioutil.ReadAll(resp.Body)				if err != nil {					log.Error(5, "%s", err)				} else {					if string(contents) != "ok" {						log.Error(5, "slack failed with: %s", string(contents))					} else {						t.IsSucceed = true					}				}			}		}	}	t.Delivered = time.Now().UTC().UnixNano()	if t.IsSucceed {		log.Trace("Hook delivered(%s): %s", t.Uuid, t.PayloadContent)	}}// DeliverHooks checks and delivers undelivered hooks.func DeliverHooks() {	tasks := make([]*HookTask, 0, 10)	x.Where("is_delivered=?", false).Iterate(new(HookTask),		func(idx int, bean interface{}) error {			t := bean.(*HookTask)			deliverHook(t)			tasks = append(tasks, t)			return nil		})	// Update hook task status.	for _, t := range tasks {		if err := UpdateHookTask(t); err != nil {			log.Error(4, "UpdateHookTask(%d): %v", t.ID, err)		}	}	HookQueue = &hookQueue{		lock:    sync.Mutex{},		repoIDs: make(map[int64]bool),		queue:   make(chan int64, setting.Webhook.QueueLength),	}	// Start listening on new hook requests.	for repoID := range HookQueue.queue {		HookQueue.removeRepoID(repoID)		tasks = make([]*HookTask, 0, 5)		if err := x.Where("repo_id=? AND is_delivered=?", repoID, false).Find(&tasks); err != nil {			log.Error(4, "Get repository(%d) hook tasks: %v", repoID, err)			continue		}		for _, t := range tasks {			deliverHook(t)			if err := UpdateHookTask(t); err != nil {				log.Error(4, "UpdateHookTask(%d): %v", t.ID, err)			}		}	}}func InitDeliverHooks() {	go DeliverHooks()}
 |