github.com/kvattikuti/drone@v0.2.1-0.20140603034306-d400229a327a/pkg/handler/gogs.go (about)

     1  package handler
     2  
     3  import (
     4  	"database/sql"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"github.com/drone/drone/pkg/build/script"
     9  	"github.com/drone/drone/pkg/database"
    10  	. "github.com/drone/drone/pkg/model"
    11  	"github.com/drone/drone/pkg/queue"
    12  	"io/ioutil"
    13  	"net/http"
    14  	"os"
    15  	"strings"
    16  	"time"
    17  )
    18  
    19  const (
    20  	droneYmlUrlPattern = "http://%s/%s/%s/raw/%s/.drone.yml"
    21  )
    22  
    23  type GogsHandler struct {
    24  	queue *queue.Queue
    25  }
    26  
    27  func NewGogsHandler(queue *queue.Queue) *GogsHandler {
    28  	return &GogsHandler{
    29  		queue: queue,
    30  	}
    31  }
    32  
    33  // Processes a generic POST-RECEIVE Gogs hook and
    34  // attempts to trigger a build.
    35  func (h *GogsHandler) Hook(w http.ResponseWriter, r *http.Request) error {
    36  
    37  	defer r.Body.Close()
    38  	payloadbytes, err := ioutil.ReadAll(r.Body)
    39  	if err != nil {
    40  		println(err.Error())
    41  		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
    42  	}
    43  	fmt.Printf("body is => %s\n", string(payloadbytes))
    44  
    45  	payload, err := ParseHook(payloadbytes)
    46  	if err != nil {
    47  		println(err.Error())
    48  		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
    49  	}
    50  	fmt.Printf("payload parsed\n")
    51  
    52  	// Verify that the commit doesn't already exist.
    53  	// We should never build the same commit twice.
    54  	_, err = database.GetCommitHash(payload.Commits[0].Id, payload.Repo.Id)
    55  	if err != nil && err != sql.ErrNoRows {
    56  		return RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway)
    57  	}
    58  	fmt.Printf("commit hash checked\n")
    59  
    60  	// Save repo to the database if needed
    61  	var urlParts = strings.Split(payload.Repo.Url, "/")
    62  
    63  	repo, err := setupRepo(urlParts, payload)
    64  	if err != nil {
    65  		println(err.Error())
    66  		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
    67  	}
    68  
    69  	commit := &Commit{}
    70  	commit.RepoID = repo.ID
    71  	commit.Branch = payload.Branch()
    72  	commit.Hash = payload.Commits[0].Id
    73  	commit.Status = "Pending"
    74  	commit.Created = time.Now().UTC()
    75  
    76  	commit.Message = payload.Commits[0].Message
    77  	commit.Timestamp = time.Now().UTC().String()
    78  	commit.SetAuthor(payload.Commits[0].Author.Name)
    79  	fmt.Printf("commit struct created\n")
    80  
    81  	// save the commit to the database
    82  	if err := database.SaveCommit(commit); err != nil {
    83  		return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
    84  	}
    85  	fmt.Printf("commit struct saved\n")
    86  
    87  	var service_endpoint = urlParts[2]
    88  	if os.Getenv("GOGS_URL") != "" {
    89  		service_endpoint = os.Getenv("GOGS_URL")
    90  	}
    91  	// GET .drone.yml file
    92  	var droneYmlUrl = fmt.Sprintf(droneYmlUrlPattern, service_endpoint, urlParts[3], urlParts[4], commit.Hash)
    93  	println("droneYmlUrl is ", droneYmlUrl)
    94  	ymlGetResponse, err := http.Get(droneYmlUrl)
    95  	var buildYml = ""
    96  	if err != nil {
    97  		println(err.Error())
    98  		return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
    99  	} else {
   100  		defer ymlGetResponse.Body.Close()
   101  		yml, err := ioutil.ReadAll(ymlGetResponse.Body)
   102  		if err != nil {
   103  			println(err.Error())
   104  			return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
   105  		}
   106  		buildYml = string(yml)
   107  		println("yml from http get: ", buildYml)
   108  	}
   109  
   110  	// parse the build script
   111  	var repoParams = map[string]string{}
   112  	println("parsing yml")
   113  	buildscript, err := script.ParseBuild([]byte(buildYml), repoParams)
   114  	if err != nil {
   115  		msg := "Could not parse your .drone.yml file.  It needs to be a valid drone yaml file.\n\n" + err.Error() + "\n"
   116  		if err := saveFailedBuild(commit, msg); err != nil {
   117  			return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
   118  		}
   119  		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
   120  	}
   121  	fmt.Printf("build script parsed\n")
   122  
   123  	// save the build to the database
   124  	build := &Build{}
   125  	build.Slug = "1" // TODO
   126  	build.CommitID = commit.ID
   127  	build.Created = time.Now().UTC()
   128  	build.Status = "Pending"
   129  	if err := database.SaveBuild(build); err != nil {
   130  		return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
   131  	}
   132  	println("build saved")
   133  
   134  	// send the build to the queue
   135  	h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript})
   136  	fmt.Printf("build task added to queue\n")
   137  
   138  	// OK!
   139  	return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
   140  }
   141  
   142  type PayloadAuthor struct {
   143  	Name  string `json:"name"`
   144  	Email string `json:"email"`
   145  }
   146  
   147  type PayloadCommit struct {
   148  	Id      string         `json:"id"`
   149  	Message string         `json:"message"`
   150  	Url     string         `json:"url"`
   151  	Author  *PayloadAuthor `json:"author"`
   152  }
   153  
   154  type PayloadRepo struct {
   155  	Id          int64          `json:"id"`
   156  	Name        string         `json:"name"`
   157  	Url         string         `json:"url"`
   158  	Description string         `json:"description"`
   159  	Website     string         `json:"website"`
   160  	Watchers    int            `json:"watchers"`
   161  	Owner       *PayloadAuthor `json:"author"`
   162  	Private     bool           `json:"private"`
   163  }
   164  
   165  // Payload represents payload information of payload.
   166  type Payload struct {
   167  	Secret  string           `json:"secret"`
   168  	Ref     string           `json:"ref"`
   169  	Commits []*PayloadCommit `json:"commits"`
   170  	Repo    *PayloadRepo     `json:"repository"`
   171  	Pusher  *PayloadAuthor   `json:"pusher"`
   172  }
   173  
   174  var ErrInvalidReceiveHook = errors.New("Invalid JSON payload received over webhook")
   175  
   176  func ParseHook(raw []byte) (*Payload, error) {
   177  
   178  	hook := Payload{}
   179  	if err := json.Unmarshal(raw, &hook); err != nil {
   180  		return nil, err
   181  	}
   182  
   183  	// it is possible the JSON was parsed, however,
   184  	// was not from Github (maybe was from Bitbucket)
   185  	// So we'll check to be sure certain key fields
   186  	// were populated
   187  	switch {
   188  	case hook.Repo == nil:
   189  		return nil, ErrInvalidReceiveHook
   190  	case len(hook.Ref) == 0:
   191  		return nil, ErrInvalidReceiveHook
   192  	}
   193  
   194  	return &hook, nil
   195  }
   196  
   197  func (h *Payload) Branch() string {
   198  	return strings.Replace(h.Ref, "refs/heads/", "", -1)
   199  }
   200  
   201  func setupRepo(urlParts []string, payload *Payload) (*Repo, error) {
   202  	println("urlParts: ", urlParts)
   203  	repo, err := database.GetRepoSlug(fmt.Sprintf("%s/%s/%s", urlParts[2], urlParts[3], urlParts[4]))
   204  	if err != nil {
   205  		if err != sql.ErrNoRows {
   206  			return nil, fmt.Errorf("error fetching repo: %s", err)
   207  		}
   208  		fmt.Errorf("Repo does not exist in database. %s", err)
   209  
   210  		// urlParts[2] will stay as-is (git.interior.vesseler as it used in url)
   211  		// need to modify payload.Repo.Url so that git clone works
   212  		repo_url := payload.Repo.Url
   213  		if os.Getenv("GOGS_URL") != "" {
   214  			repo_url = fmt.Sprintf("http://%s/%s/%s", os.Getenv("GOGS_URL"), urlParts[3], urlParts[4])
   215  		}
   216  
   217  		repo, err = NewRepo(urlParts[2], urlParts[3], urlParts[4], ScmGit, repo_url)
   218  		if err != nil {
   219  			println(err.Error())
   220  			return nil, fmt.Errorf("Repo object could not be created %s", err)
   221  		}
   222  		fmt.Printf("repo struct created\n")
   223  		user, err := database.GetUserEmail(payload.Repo.Owner.Email)
   224  		if err != nil {
   225  			return repo, fmt.Errorf("Repo could not find user with email %s, err= %s", payload.Repo.Owner.Email, err)
   226  		}
   227  		repo.UserID = user.ID
   228  		repo.Private = payload.Repo.Private
   229  
   230  		err = database.SaveRepo(repo)
   231  		if err != nil {
   232  			return repo, fmt.Errorf("Repo could not be saved to database. %s", err)
   233  		} else {
   234  			fmt.Printf("repo saved in database\n")
   235  			return repo, nil
   236  		}
   237  	}
   238  	fmt.Printf("repo exists in database\n")
   239  	return repo, nil
   240  }