Example #1
0
// ParseHook parses the post-commit hook from the Request body
// and returns the required data in a standard format.
func (r *GitHub) ParseHook(req *http.Request) (*model.Hook, error) {
	// handle github ping
	if req.Header.Get("X-Github-Event") == "ping" {
		return nil, nil
	}

	// handle github pull request hook differently
	if req.Header.Get("X-Github-Event") == "pull_request" {
		return r.ParsePullRequestHook(req)
	}

	// parse the github Hook payload
	var payload = GetPayload(req)
	var data, err = github.ParseHook(payload)
	if err != nil {
		return nil, nil
	}

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

	var hook = new(model.Hook)
	hook.Repo = data.Repo.Name
	hook.Owner = data.Repo.Owner.Login
	hook.Sha = data.Head.Id
	hook.Branch = data.Branch()

	if len(hook.Owner) == 0 {
		hook.Owner = data.Repo.Owner.Name
	}

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

	return hook, nil
}
Example #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)
}
Example #3
0
// HandleMessage reads the nsq message body and parses it as a github webhook,
// checks out the source for the repository & builds/uploads the binaries.
func (h *Handler) HandleMessage(m *nsq.Message) error {
	hook, err := github.ParseHook(m.Body)
	if err != nil {
		// Errors will most likely occur because not all GH
		// hooks are the same format
		// we care about those that are pushes to master
		logrus.Debugf("Error parsing hook: %v", err)
		return nil
	}

	shortSha := hook.After[0:7]
	// checkout the code in a temp dir
	temp, err := ioutil.TempDir("", fmt.Sprintf("commit-%s", shortSha))
	if err != nil {
		return err
	}
	defer os.RemoveAll(temp)

	if err := checkout(temp, hook.Repo.Url, hook.After); err != nil {
		logrus.Warn(err)
		return err
	}
	logrus.Debugf("Checked out %s for %s", hook.After, hook.Repo.Url)

	var (
		image     = fmt.Sprintf("docker:commit-%s", shortSha)
		container = fmt.Sprintf("build-%s", shortSha)
	)
	logrus.Infof("image=%s container=%s\n", image, container)

	// build the image
	if err := build(temp, image); err != nil {
		logrus.Warn(err)
		return err
	}
	logrus.Debugf("Successfully built image %s", image)

	// make the binary
	defer removeContainer(container)
	if err = makeBinary(temp, image, container, 20*time.Minute); err != nil {
		logrus.Warn(err)
		return err
	}
	logrus.Debugf("Successfully built binaries for %s", hook.After)

	// read the version
	version, err := getBinaryVersion(temp)
	if err != nil {
		logrus.Warnf("Getting binary version failed: %v", err)
		return err
	}

	bundlesPath := path.Join(temp, "bundles", version, "cross")

	// create commit file
	if err := ioutil.WriteFile(path.Join(bundlesPath, "commit"), []byte(hook.After), 0755); err != nil {
		return err
	}

	// create version file
	if err := ioutil.WriteFile(path.Join(bundlesPath, "version"), []byte(version), 0755); err != nil {
		return err
	}

	// use env variables to connect to s3
	auth, err := aws.EnvAuth()
	if err != nil {
		return fmt.Errorf("AWS Auth failed: %v", err)
	}

	// connect to s3 bucket
	s := s3.New(auth, aws.GetRegion(region))
	bucketname, bucketpath := bucketParts(bucket)
	bucket := s.Bucket(bucketname)

	// push to s3
	if err = pushToS3(bucket, bucketpath, bundlesPath); err != nil {
		logrus.Warn(err)
		return err
	}

	// add html to template
	if err := createIndexFile(bucket, bucketpath); err != nil {
		logrus.Warn(err)
		return err
	}

	return nil
}