github.com/unclejack/drone@v0.2.1-0.20140918182345-831b034aa33b/pkg/handler/github.go (about)

     1  package handler
     2  
     3  import (
     4  	"database/sql"
     5  	"net/http"
     6  	"strconv"
     7  	"time"
     8  
     9  	"github.com/drone/drone/pkg/database"
    10  	. "github.com/drone/drone/pkg/model"
    11  	"github.com/drone/drone/pkg/queue"
    12  	"github.com/drone/go-github/github"
    13  )
    14  
    15  type GithubHandler struct {
    16  	queue *queue.Queue
    17  }
    18  
    19  func NewGithubHandler(queue *queue.Queue) *GithubHandler {
    20  	return &GithubHandler{
    21  		queue: queue,
    22  	}
    23  }
    24  
    25  // Processes a generic POST-RECEIVE GitHub hook and
    26  // attempts to trigger a build.
    27  func (h *GithubHandler) Hook(w http.ResponseWriter, r *http.Request) error {
    28  	// handle github ping
    29  	if r.Header.Get("X-Github-Event") == "ping" {
    30  		return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
    31  	}
    32  
    33  	// if this is a pull request route
    34  	// to a different handler
    35  	if r.Header.Get("X-Github-Event") == "pull_request" {
    36  		h.PullRequestHook(w, r)
    37  		return nil
    38  	}
    39  
    40  	// get the payload of the message
    41  	// this should contain a json representation of the
    42  	// repository and commit details
    43  	payload := r.FormValue("payload")
    44  
    45  	// parse the github Hook payload
    46  	hook, err := github.ParseHook([]byte(payload))
    47  	if err != nil {
    48  		println("could not parse hook:", err.Error())
    49  		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
    50  	}
    51  
    52  	// make sure this is being triggered because of a commit
    53  	// and not something like a tag deletion or whatever
    54  	if hook.IsTag() || hook.IsGithubPages() ||
    55  		hook.IsHead() == false || hook.IsDeleted() {
    56  		return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
    57  	}
    58  
    59  	// get the repo from the URL
    60  	repoId := r.FormValue("id")
    61  
    62  	// get the repo from the database, return error if not found
    63  	repo, err := database.GetRepoSlug(repoId)
    64  	if err != nil {
    65  		return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
    66  	}
    67  
    68  	// Get the user that owns the repository
    69  	user, err := database.GetUser(repo.UserID)
    70  	if err != nil {
    71  		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
    72  	}
    73  
    74  	// Verify that the commit doesn't already exist.
    75  	// We should never build the same commit twice.
    76  	_, err = database.GetCommitBranchHash(hook.Branch(), hook.Head.Id, repo.ID)
    77  	if err != nil && err != sql.ErrNoRows {
    78  		println("commit already exists")
    79  		return RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway)
    80  	}
    81  
    82  	// we really only need:
    83  	//  * repo owner
    84  	//  * repo name
    85  	//  * repo host (github)
    86  	//  * commit hash
    87  	//  * commit timestamp
    88  	//  * commit branch
    89  	//  * commit message
    90  	//  * commit author
    91  	//  * pull request
    92  
    93  	// once we have this data we could just send directly to the queue
    94  	// and let it handle everything else
    95  
    96  	commit := &Commit{}
    97  	commit.RepoID = repo.ID
    98  	commit.Branch = hook.Branch()
    99  	commit.Hash = hook.Head.Id
   100  	commit.Status = "Pending"
   101  	commit.Created = time.Now().UTC()
   102  
   103  	// extract the author and message from the commit
   104  	// this is kind of experimental, since I don't know
   105  	// what I'm doing here.
   106  	if hook.Head != nil && hook.Head.Author != nil {
   107  		commit.Message = hook.Head.Message
   108  		commit.Timestamp = hook.Head.Timestamp
   109  		commit.SetAuthor(hook.Head.Author.Email)
   110  	} else if hook.Commits != nil && len(hook.Commits) > 0 && hook.Commits[0].Author != nil {
   111  		commit.Message = hook.Commits[0].Message
   112  		commit.Timestamp = hook.Commits[0].Timestamp
   113  		commit.SetAuthor(hook.Commits[0].Author.Email)
   114  	}
   115  
   116  	// get the github settings from the database
   117  	settings := database.SettingsMust()
   118  
   119  	// get the drone.yml file from GitHub
   120  	client := github.New(user.GithubToken)
   121  	client.ApiUrl = settings.GitHubApiUrl
   122  
   123  	content, err := client.Contents.FindRef(repo.Owner, repo.Name, ".drone.yml", commit.Hash)
   124  	if err != nil {
   125  		msg := "No .drone.yml was found in this repository.  You need to add one.\n"
   126  		if err := saveFailedBuild(commit, msg); err != nil {
   127  			return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
   128  		}
   129  		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
   130  	}
   131  
   132  	// decode the content.  Note: Not sure this will ever happen...it basically means a GitHub API issue
   133  	buildscript, err := content.DecodeContent()
   134  	if err != nil {
   135  		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
   136  	}
   137  
   138  	// save the commit to the database
   139  	if err := database.SaveCommit(commit); err != nil {
   140  		return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
   141  	}
   142  
   143  	// save the build to the database
   144  	build := &Build{}
   145  	build.Slug = "1" // TODO
   146  	build.CommitID = commit.ID
   147  	build.Created = time.Now().UTC()
   148  	build.Status = "Pending"
   149  	build.BuildScript = string(buildscript)
   150  	if err := database.SaveBuild(build); err != nil {
   151  		return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
   152  	}
   153  
   154  	// notify websocket that a new build is pending
   155  	//realtime.CommitPending(repo.UserID, repo.TeamID, repo.ID, commit.ID, repo.Private)
   156  	//realtime.BuildPending(repo.UserID, repo.TeamID, repo.ID, commit.ID, build.ID, repo.Private)
   157  
   158  	h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build}) //Push(repo, commit, build)
   159  
   160  	// OK!
   161  	return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
   162  }
   163  
   164  func (h *GithubHandler) PullRequestHook(w http.ResponseWriter, r *http.Request) {
   165  	// get the payload of the message
   166  	// this should contain a json representation of the
   167  	// repository and commit details
   168  	payload := r.FormValue("payload")
   169  
   170  	println("GOT PR HOOK")
   171  	println(payload)
   172  
   173  	hook, err := github.ParsePullRequestHook([]byte(payload))
   174  	if err != nil {
   175  		RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
   176  		return
   177  	}
   178  
   179  	// ignore these
   180  	if hook.Action != "opened" && hook.Action != "synchronize" {
   181  		RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
   182  		return
   183  	}
   184  
   185  	// get the repo from the URL
   186  	repoId := r.FormValue("id")
   187  
   188  	// get the repo from the database, return error if not found
   189  	repo, err := database.GetRepoSlug(repoId)
   190  	if err != nil {
   191  		RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
   192  		return
   193  	}
   194  
   195  	// Get the user that owns the repository
   196  	user, err := database.GetUser(repo.UserID)
   197  	if err != nil {
   198  		RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
   199  		return
   200  	}
   201  
   202  	// Verify that the commit doesn't already exist.
   203  	// We should enver build the same commit twice.
   204  	_, err = database.GetCommitHash(hook.PullRequest.Head.Sha, repo.ID)
   205  	if err != nil && err != sql.ErrNoRows {
   206  		RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway)
   207  		return
   208  	}
   209  
   210  	///////////////////////////////////////////////////////
   211  
   212  	commit := &Commit{}
   213  	commit.RepoID = repo.ID
   214  	commit.Branch = hook.PullRequest.Head.Ref
   215  	commit.Hash = hook.PullRequest.Head.Sha
   216  	commit.Status = "Pending"
   217  	commit.Created = time.Now().UTC()
   218  	commit.Gravatar = hook.PullRequest.User.GravatarId
   219  	commit.Author = hook.PullRequest.User.Login
   220  	commit.PullRequest = strconv.Itoa(hook.Number)
   221  	commit.Message = hook.PullRequest.Title
   222  	// label := p.PullRequest.Head.Labe
   223  
   224  	// get the github settings from the database
   225  	settings := database.SettingsMust()
   226  
   227  	// get the drone.yml file from GitHub
   228  	client := github.New(user.GithubToken)
   229  	client.ApiUrl = settings.GitHubApiUrl
   230  
   231  	content, err := client.Contents.FindRef(repo.Owner, repo.Name, ".drone.yml", commit.Hash) // TODO should this really be the hash??
   232  	if err != nil {
   233  		println(err.Error())
   234  		RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
   235  		return
   236  	}
   237  
   238  	// decode the content
   239  	buildscript, err := content.DecodeContent()
   240  	if err != nil {
   241  		RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
   242  		return
   243  	}
   244  
   245  	// save the commit to the database
   246  	if err := database.SaveCommit(commit); err != nil {
   247  		RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
   248  		return
   249  	}
   250  
   251  	// save the build to the database
   252  	build := &Build{}
   253  	build.Slug = "1" // TODO
   254  	build.CommitID = commit.ID
   255  	build.Created = time.Now().UTC()
   256  	build.Status = "Pending"
   257  	build.BuildScript = string(buildscript)
   258  	if err := database.SaveBuild(build); err != nil {
   259  		RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
   260  		return
   261  	}
   262  
   263  	// notify websocket that a new build is pending
   264  	// TODO we should, for consistency, just put this inside Queue.Add()
   265  	h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build})
   266  
   267  	// OK!
   268  	RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
   269  }