Beispiel #1
0
func PullRequestHook(w http.ResponseWriter, r *http.Request) {

	// get the payload of the message
	// this should contain a json representation of the
	// repository and commit details
	payload := r.FormValue("payload")

	println("GOT PR HOOK")
	println(payload)

	hook, err := github.ParsePullRequestHook([]byte(payload))
	if err != nil {
		RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
		return
	}

	// ignore these
	if hook.Action != "opened" && hook.Action != "synchronize" {
		RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
		return
	}

	// get the repo from the URL
	repoId := r.FormValue("id")

	// get the repo from the database, return error if not found
	repo, err := database.GetRepoSlug(repoId)
	if err != nil {
		RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
		return
	}

	// Get the user that owns the repository
	user, err := database.GetUser(repo.UserID)
	if err != nil {
		RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
		return
	}

	// Verify that the commit doesn't already exist.
	// We should enver build the same commit twice.
	_, err = database.GetCommitHash(hook.PullRequest.Head.Sha, repo.ID)
	if err != nil && err != sql.ErrNoRows {
		RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway)
		return
	}

	///////////////////////////////////////////////////////

	commit := &Commit{}
	commit.RepoID = repo.ID
	commit.Branch = hook.PullRequest.Head.Ref
	commit.Hash = hook.PullRequest.Head.Sha
	commit.Status = "Pending"
	commit.Created = time.Now().UTC()
	commit.Gravatar = hook.PullRequest.User.GravatarId
	commit.PullRequest = strconv.Itoa(hook.Number)
	commit.Message = hook.PullRequest.Title
	// label := p.PullRequest.Head.Labe

	// get the drone.yml file from GitHub
	client := github.New(user.GithubToken)
	content, err := client.Contents.FindRef(repo.Owner, repo.Name, ".drone.yml", commit.Hash) // TODO should this really be the hash??
	if err != nil {
		println(err.Error())
		RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
		return
	}

	// decode the content
	raw, err := content.DecodeContent()
	if err != nil {
		RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
		return
	}

	// parse the build script
	buildscript, err := script.ParseBuild(raw)
	if err != nil {
		// TODO if the YAML is invalid we should create a commit record
		// with an ERROR status so that the user knows why a build wasn't
		// triggered in the system
		RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
		return
	}

	// save the commit to the database
	if err := database.SaveCommit(commit); err != nil {
		RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		return
	}

	// save the build to the database
	build := &Build{}
	build.Slug = "1" // TODO
	build.CommitID = commit.ID
	build.Created = time.Now().UTC()
	build.Status = "Pending"
	if err := database.SaveBuild(build); err != nil {
		RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		return
	}

	// notify websocket that a new build is pending
	// TODO we should, for consistency, just put this inside Queue.Add()
	queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript})

	// OK!
	RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
}
Beispiel #2
0
// Processes a generic POST-RECEIVE hook and
// attempts to trigger a build.
func Hook(w http.ResponseWriter, r *http.Request) error {

	// if this is a pull request route
	// to a different handler
	if r.Header.Get("X-Github-Event") == "pull_request" {
		PullRequestHook(w, r)
		return nil
	}

	// get the payload of the message
	// this should contain a json representation of the
	// repository and commit details
	payload := r.FormValue("payload")

	// parse the github Hook payload
	hook, err := github.ParseHook([]byte(payload))
	if err != nil {
		println("could not parse hook")
		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
	}

	// make sure this is being triggered because of a commit
	// and not something like a tag deletion or whatever
	if hook.IsTag() || hook.IsGithubPages() ||
		hook.IsHead() == false || hook.IsDeleted() {
		return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
	}

	// get the repo from the URL
	repoId := r.FormValue("id")

	// get the repo from the database, return error if not found
	repo, err := database.GetRepoSlug(repoId)
	if err != nil {
		return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
	}

	// Get the user that owns the repository
	user, err := database.GetUser(repo.UserID)
	if err != nil {
		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
	}

	// Verify that the commit doesn't already exist.
	// We should never build the same commit twice.
	_, err = database.GetCommitHash(hook.Head.Id, repo.ID)
	if err != nil && err != sql.ErrNoRows {
		println("commit already exists")
		return RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway)
	}

	// we really only need:
	//  * repo owner
	//  * repo name
	//  * repo host (github)
	//  * commit hash
	//  * commit timestamp
	//  * commit branch
	//  * commit message
	//  * commit author
	//  * pull request

	// once we have this data we could just send directly to the queue
	// and let it handle everything else

	commit := &Commit{}
	commit.RepoID = repo.ID
	commit.Branch = hook.Branch()
	commit.Hash = hook.Head.Id
	commit.Status = "Pending"
	commit.Created = time.Now().UTC()

	// extract the author and message from the commit
	// this is kind of experimental, since I don't know
	// what I'm doing here.
	if hook.Head != nil && hook.Head.Author != nil {
		commit.Message = hook.Head.Message
		commit.Timestamp = hook.Head.Timestamp
		commit.SetAuthor(hook.Head.Author.Email)
	} else if hook.Commits != nil && len(hook.Commits) > 0 && hook.Commits[0].Author != nil {
		commit.Message = hook.Commits[0].Message
		commit.Timestamp = hook.Commits[0].Timestamp
		commit.SetAuthor(hook.Commits[0].Author.Email)
	}

	// get the drone.yml file from GitHub
	client := github.New(user.GithubToken)
	content, err := client.Contents.FindRef(repo.Owner, repo.Name, ".drone.yml", commit.Hash)
	if err != nil {
		msg := "No .drone.yml was found in this repository.  You need to add one.\n"
		if err := saveFailedBuild(commit, msg); err != nil {
			return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		}
		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
	}

	// decode the content.  Note: Not sure this will ever happen...it basically means a GitHub API issue
	raw, err := content.DecodeContent()
	if err != nil {
		msg := "Could not decode the yaml from GitHub.  Check that your .drone.yml is a valid yaml file.\n"
		if err := saveFailedBuild(commit, msg); err != nil {
			return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		}
		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
	}

	// parse the build script
	buildscript, err := script.ParseBuild(raw)
	if err != nil {
		msg := "Could not parse your .drone.yml file.  It needs to be a valid drone yaml file.\n\n" + err.Error() + "\n"
		if err := saveFailedBuild(commit, msg); err != nil {
			return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		}
		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
	}

	// save the commit to the database
	if err := database.SaveCommit(commit); err != nil {
		return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
	}

	// save the build to the database
	build := &Build{}
	build.Slug = "1" // TODO
	build.CommitID = commit.ID
	build.Created = time.Now().UTC()
	build.Status = "Pending"
	if err := database.SaveBuild(build); err != nil {
		return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
	}

	// notify websocket that a new build is pending
	//realtime.CommitPending(repo.UserID, repo.TeamID, repo.ID, commit.ID, repo.Private)
	//realtime.BuildPending(repo.UserID, repo.TeamID, repo.ID, commit.ID, build.ID, repo.Private)

	queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript}) //Push(repo, commit, build, buildscript)

	// OK!
	return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
}
Beispiel #3
0
// Processes a generic POST-RECEIVE Bitbucket hook and
// attempts to trigger a build.
func (h *BitbucketHandler) Hook(w http.ResponseWriter, r *http.Request) error {
	// get the payload from the request
	payload := r.FormValue("payload")

	// parse the post-commit hook
	hook, err := bitbucket.ParseHook([]byte(payload))
	if err != nil {
		return err
	}

	// get the repo from the URL
	repoId := r.FormValue("id")

	// get the repo from the database, return error if not found
	repo, err := database.GetRepoSlug(repoId)
	if err != nil {
		return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
	}

	// Get the user that owns the repository
	user, err := database.GetUser(repo.UserID)
	if err != nil {
		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
	}

	// Verify that the commit doesn't already exist.
	// We should never build the same commit twice.
	_, err = database.GetCommitHash(hook.Commits[len(hook.Commits)-1].Hash, repo.ID)
	if err != nil && err != sql.ErrNoRows {
		return RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway)
	}

	commit := &Commit{}
	commit.RepoID = repo.ID
	commit.Branch = hook.Commits[len(hook.Commits)-1].Branch
	commit.Hash = hook.Commits[len(hook.Commits)-1].Hash
	commit.Status = "Pending"
	commit.Created = time.Now().UTC()
	commit.Message = hook.Commits[len(hook.Commits)-1].Message
	commit.Timestamp = time.Now().UTC().String()
	commit.SetAuthor(hook.Commits[len(hook.Commits)-1].Author)

	// get the github settings from the database
	settings := database.SettingsMust()

	// create the Bitbucket client
	client := bitbucket.New(
		settings.BitbucketKey,
		settings.BitbucketSecret,
		user.BitbucketToken,
		user.BitbucketSecret,
	)

	// get the yaml from the database
	raw, err := client.Sources.Find(repo.Owner, repo.Name, commit.Hash, ".drone.yml")
	if err != nil {
		return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
	}

	// parse the build script
	buildscript, err := script.ParseBuild([]byte(raw.Data), repo.Params)
	if err != nil {
		msg := "Could not parse your .drone.yml file.  It needs to be a valid drone yaml file.\n\n" + err.Error() + "\n"
		if err := saveFailedBuild(commit, msg); err != nil {
			return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		}
		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
	}

	// save the commit to the database
	if err := database.SaveCommit(commit); err != nil {
		return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
	}

	// save the build to the database
	build := &Build{}
	build.Slug = "1" // TODO
	build.CommitID = commit.ID
	build.Created = time.Now().UTC()
	build.Status = "Pending"
	if err := database.SaveBuild(build); err != nil {
		return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
	}

	// send the build to the queue
	h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript})

	// OK!
	return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
}
Beispiel #4
0
// Processes a generic POST-RECEIVE Gogs hook and
// attempts to trigger a build.
func (h *GogsHandler) Hook(w http.ResponseWriter, r *http.Request) error {

	defer r.Body.Close()
	payloadbytes, err := ioutil.ReadAll(r.Body)
	if err != nil {
		println(err.Error())
		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
	}
	fmt.Printf("body is => %s\n", string(payloadbytes))

	payload, err := ParseHook(payloadbytes)
	if err != nil {
		println(err.Error())
		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
	}
	fmt.Printf("payload parsed\n")

	// Verify that the commit doesn't already exist.
	// We should never build the same commit twice.
	_, err = database.GetCommitHash(payload.Commits[0].Id, payload.Repo.Id)
	if err != nil && err != sql.ErrNoRows {
		return RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway)
	}
	fmt.Printf("commit hash checked\n")

	// Save repo to the database if needed
	var urlParts = strings.Split(payload.Repo.Url, "/")

	repo, err := setupRepo(urlParts, payload)
	if err != nil {
		println(err.Error())
		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
	}

	commit := &Commit{}
	commit.RepoID = repo.ID
	commit.Branch = payload.Branch()
	commit.Hash = payload.Commits[0].Id
	commit.Status = "Pending"
	commit.Created = time.Now().UTC()

	commit.Message = payload.Commits[0].Message
	commit.Timestamp = time.Now().UTC().String()
	commit.SetAuthor(payload.Commits[0].Author.Name)
	fmt.Printf("commit struct created\n")

	// save the commit to the database
	if err := database.SaveCommit(commit); err != nil {
		return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
	}
	fmt.Printf("commit struct saved\n")

	var service_endpoint = urlParts[2]
	if os.Getenv("GOGS_URL") != "" {
		service_endpoint = os.Getenv("GOGS_URL")
	}
	// GET .drone.yml file
	var droneYmlUrl = fmt.Sprintf(droneYmlUrlPattern, service_endpoint, urlParts[3], urlParts[4], commit.Hash)
	println("droneYmlUrl is ", droneYmlUrl)
	ymlGetResponse, err := http.Get(droneYmlUrl)
	var buildYml = ""
	if err != nil {
		println(err.Error())
		return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
	} else {
		defer ymlGetResponse.Body.Close()
		yml, err := ioutil.ReadAll(ymlGetResponse.Body)
		if err != nil {
			println(err.Error())
			return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		}
		buildYml = string(yml)
		println("yml from http get: ", buildYml)
	}

	// parse the build script
	var repoParams = map[string]string{}
	println("parsing yml")
	buildscript, err := script.ParseBuild([]byte(buildYml), repoParams)
	if err != nil {
		msg := "Could not parse your .drone.yml file.  It needs to be a valid drone yaml file.\n\n" + err.Error() + "\n"
		if err := saveFailedBuild(commit, msg); err != nil {
			return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		}
		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
	}
	fmt.Printf("build script parsed\n")

	// save the build to the database
	build := &Build{}
	build.Slug = "1" // TODO
	build.CommitID = commit.ID
	build.Created = time.Now().UTC()
	build.Status = "Pending"
	if err := database.SaveBuild(build); err != nil {
		return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
	}
	println("build saved")

	// send the build to the queue
	h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript})
	fmt.Printf("build task added to queue\n")

	// OK!
	return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
}
Beispiel #5
0
// execute will execute the build task and persist
// the results to the datastore.
func (w *worker) execute(task *BuildTask) error {
	// we need to be sure that we can recover
	// from any sort panic that could occur
	// to avoid brining down the entire application
	defer func() {
		if e := recover(); e != nil {
			task.Build.Finished = time.Now().UTC()
			task.Commit.Finished = time.Now().UTC()
			task.Build.Duration = task.Build.Finished.Unix() - task.Build.Started.Unix()
			task.Commit.Duration = task.Build.Finished.Unix() - task.Build.Started.Unix()
			task.Commit.Status = "Error"
			task.Build.Status = "Error"
			database.SaveBuild(task.Build)
			database.SaveCommit(task.Commit)
		}
	}()

	// update commit and build status
	task.Commit.Status = "Started"
	task.Build.Status = "Started"
	task.Build.Started = time.Now().UTC()
	task.Commit.Started = time.Now().UTC()

	// persist the commit to the database
	if err := database.SaveCommit(task.Commit); err != nil {
		return err
	}

	// persist the build to the database
	if err := database.SaveBuild(task.Build); err != nil {
		return err
	}

	// get settings
	settings, _ := database.GetSettings()

	// notification context
	context := &notify.Context{
		Repo:   task.Repo,
		Commit: task.Commit,
		Host:   settings.URL().String(),
	}

	// parse the build script
	buildscript, err := script.ParseBuild([]byte(task.Build.BuildScript), task.Repo.Params)
	if err != nil {
		log.Printf("Could not parse your .drone.yml file.  It needs to be a valid drone yaml file.\n\n" + err.Error() + "\n")
		return err
	}

	// send all "started" notifications
	if buildscript.Notifications != nil {
		buildscript.Notifications.Send(context)
	}

	// Send "started" notification to Github
	if err := updateGitHubStatus(task.Repo, task.Commit); err != nil {
		log.Printf("error updating github status: %s\n", err.Error())
	}

	// make sure a channel exists for the repository,
	// the commit, and the commit output (TODO)
	reposlug := fmt.Sprintf("%s/%s/%s", task.Repo.Host, task.Repo.Owner, task.Repo.Name)
	commitslug := fmt.Sprintf("%s/%s/%s/commit/%s/%s", task.Repo.Host, task.Repo.Owner, task.Repo.Name, task.Commit.Branch, task.Commit.Hash)
	consoleslug := fmt.Sprintf("%s/%s/%s/commit/%s/%s/builds/%s", task.Repo.Host, task.Repo.Owner, task.Repo.Name, task.Commit.Branch, task.Commit.Hash, task.Build.Slug)
	channel.Create(reposlug)
	channel.Create(commitslug)
	channel.CreateStream(consoleslug)

	// notify the channels that the commit and build started
	channel.SendJSON(reposlug, task.Commit)
	channel.SendJSON(commitslug, task.Build)

	var buf = &bufferWrapper{channel: consoleslug}

	// append private parameters to the environment
	// variable section of the .drone.yml file, iff
	// this is not a pull request (for security purposes)
	if task.Repo.Params != nil && len(task.Commit.PullRequest) == 0 {
		for k, v := range task.Repo.Params {
			buildscript.Env = append(buildscript.Env, k+"="+v)
		}
	}

	defer func() {
		// update the status of the commit using the
		// GitHub status API.
		if err := updateGitHubStatus(task.Repo, task.Commit); err != nil {
			log.Printf("error updating github status: %s\n", err.Error())
		}
	}()

	// execute the build
	passed, buildErr := w.runBuild(task, buildscript, buf)

	task.Build.Finished = time.Now().UTC()
	task.Commit.Finished = time.Now().UTC()
	task.Build.Duration = task.Build.Finished.UnixNano() - task.Build.Started.UnixNano()
	task.Commit.Duration = task.Build.Finished.UnixNano() - task.Build.Started.UnixNano()
	task.Commit.Status = "Success"
	task.Build.Status = "Success"
	task.Build.Stdout = buf.buf.String()

	// if exit code != 0 set to failure
	if passed {
		task.Commit.Status = "Failure"
		task.Build.Status = "Failure"
		if buildErr != nil && task.Build.Stdout == "" {
			// TODO: If you wanted to have very friendly error messages, you could do that here
			task.Build.Stdout = buildErr.Error() + "\n"
		}
	}

	// persist the build to the database
	if err := database.SaveBuild(task.Build); err != nil {
		return err
	}

	// persist the commit to the database
	if err := database.SaveCommit(task.Commit); err != nil {
		return err
	}

	// notify the channels that the commit and build finished
	channel.SendJSON(reposlug, task.Commit)
	channel.SendJSON(commitslug, task.Build)
	channel.Close(consoleslug)

	// send all "finished" notifications
	if buildscript.Notifications != nil {
		buildscript.Notifications.Send(context)
	}

	return nil
}
Beispiel #6
0
func (g *GitlabHandler) PullRequestHook(p *gogitlab.HookPayload, repo *Repo, user *User) error {
	obj := p.ObjectAttributes

	// Gitlab may trigger multiple hooks upon updating merge requests status
	// only build when it was just opened and the merge hasn't been checked yet.
	if !(obj.State == "opened" && obj.MergeStatus == "unchecked") {
		fmt.Println("Ignore GitLab Merge Requests")
		return nil
	}

	settings := database.SettingsMust()

	client := gogitlab.NewGitlab(settings.GitlabApiUrl, g.apiPath, user.GitlabToken)

	// GitLab merge-requests hook doesn't include repository data.
	// Have to fetch it manually
	src, err := client.RepoBranch(strconv.Itoa(obj.SourceProjectId), obj.SourceBranch)
	if err != nil {
		return err
	}

	_, err = database.GetCommitHash(src.Commit.Id, repo.ID)
	if err != nil && err != sql.ErrNoRows {
		fmt.Println("commit already exists")
		return err
	}

	commit := &Commit{}
	commit.RepoID = repo.ID
	commit.Branch = src.Name
	commit.Hash = src.Commit.Id
	commit.Status = "Pending"
	commit.Created = time.Now().UTC()
	commit.PullRequest = strconv.Itoa(obj.IId)

	commit.Message = src.Commit.Message
	commit.Timestamp = src.Commit.AuthoredDateRaw
	commit.SetAuthor(src.Commit.Author.Email)

	content, err := client.RepoRawFile(strconv.Itoa(obj.SourceProjectId), commit.Hash, ".drone.yml")
	if err != nil {
		msg := "No .drone.yml was found in this repository.  You need to add one.\n"
		if err := saveFailedBuild(commit, msg); err != nil {
			return fmt.Errorf("Failed to save build: %q", err)
		}
		return fmt.Errorf("Error to fetch build script: %q", err)
	}

	// parse the build script
	buildscript, err := script.ParseBuild(content, repo.Params)
	if err != nil {
		msg := "Could not parse your .drone.yml file.  It needs to be a valid drone yaml file.\n\n" + err.Error() + "\n"
		if err := saveFailedBuild(commit, msg); err != nil {
			return fmt.Errorf("Failed to save build: %q", err)
		}
		return fmt.Errorf("Failed to parse build script: %q", err)
	}

	// save the commit to the database
	if err := database.SaveCommit(commit); err != nil {
		return fmt.Errorf("Failed to save commit: %q", err)
	}

	// save the build to the database
	build := &Build{}
	build.Slug = "1" // TODO
	build.CommitID = commit.ID
	build.Created = time.Now().UTC()
	build.Status = "Pending"
	if err := database.SaveBuild(build); err != nil {
		return fmt.Errorf("Failed to save build: %q", err)
	}

	g.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript})

	return nil
}
Beispiel #7
0
func (g *GitlabHandler) Hook(w http.ResponseWriter, r *http.Request) error {
	rID := r.FormValue("id")
	repo, err := database.GetRepoSlug(rID)
	if err != nil {
		return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
	}

	user, err := database.GetUser(repo.UserID)
	if err != nil {
		return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
	}

	payload, _ := ioutil.ReadAll(r.Body)
	parsed, err := gogitlab.ParseHook(payload)
	if err != nil {
		return err
	}
	if parsed.ObjectKind == "merge_request" {
		fmt.Println(string(payload))
		if err := g.PullRequestHook(parsed, repo, user); err != nil {
			return err
		}
		return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
	}

	if len(parsed.After) == 0 {
		return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
	}

	_, err = database.GetCommitHash(parsed.After, repo.ID)
	if err != nil && err != sql.ErrNoRows {
		fmt.Println("commit already exists")
		return RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway)
	}

	commit := &Commit{}
	commit.RepoID = repo.ID
	commit.Branch = parsed.Branch()
	commit.Hash = parsed.After
	commit.Status = "Pending"
	commit.Created = time.Now().UTC()

	head := parsed.Head()
	commit.Message = head.Message
	commit.Timestamp = head.Timestamp
	if head.Author != nil {
		commit.SetAuthor(head.Author.Email)
	} else {
		commit.SetAuthor(parsed.UserName)
	}

	// get the github settings from the database
	settings := database.SettingsMust()

	// get the drone.yml file from GitHub
	client := gogitlab.NewGitlab(settings.GitlabApiUrl, g.apiPath, user.GitlabToken)

	content, err := client.RepoRawFile(ns(repo.Owner, repo.Name), commit.Hash, ".drone.yml")
	if err != nil {
		msg := "No .drone.yml was found in this repository.  You need to add one.\n"
		if err := saveFailedBuild(commit, msg); err != nil {
			return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		}
		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
	}

	// parse the build script
	buildscript, err := script.ParseBuild(content, repo.Params)
	if err != nil {
		msg := "Could not parse your .drone.yml file.  It needs to be a valid drone yaml file.\n\n" + err.Error() + "\n"
		if err := saveFailedBuild(commit, msg); err != nil {
			return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		}
		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
	}

	// save the commit to the database
	if err := database.SaveCommit(commit); err != nil {
		return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
	}

	// save the build to the database
	build := &Build{}
	build.Slug = "1" // TODO
	build.CommitID = commit.ID
	build.Created = time.Now().UTC()
	build.Status = "Pending"
	if err := database.SaveBuild(build); err != nil {
		return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
	}

	g.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript})

	// OK!
	return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)

}